Lokasi ngalangkungan proxy:   [ UP ]  
[Ngawartoskeun bug]   [Panyetelan cookie]                
Skip to content

Commit 767914b

Browse files
imduffy15jen20
authored andcommitted
[hashicorpGH-1275] Support for AWS access via IAMs AssumeRole functionality
This commit enables terraform to utilise the assume role functionality of sts to execute commands with different privileges than the API keys specified. Signed-off-by: Ian Duffy <ian@ianduffy.ie>
1 parent 0cf4341 commit 767914b

6 files changed

Lines changed: 108 additions & 16 deletions

File tree

builtin/providers/aws/auth_helpers.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import (
1111
"github.com/aws/aws-sdk-go/aws/awserr"
1212
awsCredentials "github.com/aws/aws-sdk-go/aws/credentials"
1313
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
14+
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
1415
"github.com/aws/aws-sdk-go/aws/ec2metadata"
1516
"github.com/aws/aws-sdk-go/aws/session"
1617
"github.com/aws/aws-sdk-go/service/iam"
1718
"github.com/aws/aws-sdk-go/service/sts"
1819
"github.com/hashicorp/errwrap"
1920
"github.com/hashicorp/go-cleanhttp"
21+
"github.com/hashicorp/go-multierror"
2022
)
2123

2224
func GetAccountId(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string) (string, error) {
@@ -92,7 +94,9 @@ func parseAccountIdFromArn(arn string) (string, error) {
9294
// This function is responsible for reading credentials from the
9395
// environment in the case that they're not explicitly specified
9496
// in the Terraform configuration.
95-
func GetCredentials(c *Config) *awsCredentials.Credentials {
97+
func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {
98+
var errs []error
99+
96100
// build a chain provider, lazy-evaulated by aws-sdk
97101
providers := []awsCredentials.Provider{
98102
&awsCredentials.StaticProvider{Value: awsCredentials.Value{
@@ -137,7 +141,40 @@ func GetCredentials(c *Config) *awsCredentials.Credentials {
137141
}
138142
}
139143

140-
return awsCredentials.NewChainCredentials(providers)
144+
if c.RoleArn != "" {
145+
log.Printf("[INFO] attempting to assume role %s", c.RoleArn)
146+
147+
creds := awsCredentials.NewChainCredentials(providers)
148+
cp, err := creds.Get()
149+
if err != nil {
150+
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
151+
errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS Provider.
152+
Please see https://terraform.io/docs/providers/aws/index.html for more information on
153+
providing credentials for the AWS Provider`))
154+
} else {
155+
errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err))
156+
}
157+
return nil, &multierror.Error{Errors: errs}
158+
}
159+
160+
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
161+
162+
awsConfig := &aws.Config{
163+
Credentials: creds,
164+
Region: aws.String(c.Region),
165+
MaxRetries: aws.Int(c.MaxRetries),
166+
HTTPClient: cleanhttp.DefaultClient(),
167+
S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
168+
}
169+
170+
stsclient := sts.New(session.New(awsConfig))
171+
providers = []awsCredentials.Provider{&stscreds.AssumeRoleProvider{
172+
Client: stsclient,
173+
RoleARN: c.RoleArn,
174+
}}
175+
}
176+
177+
return awsCredentials.NewChainCredentials(providers), nil
141178
}
142179

143180
func setOptionalEndpoint(cfg *aws.Config) string {

builtin/providers/aws/auth_helpers_test.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,13 @@ func TestAWSGetCredentials_shouldError(t *testing.T) {
218218
defer resetEnv()
219219
cfg := Config{}
220220

221-
c := GetCredentials(&cfg)
222-
_, err := c.Get()
221+
c, err := GetCredentials(&cfg)
222+
if awsErr, ok := err.(awserr.Error); ok {
223+
if awsErr.Code() != "NoCredentialProviders" {
224+
t.Fatalf("Expected NoCredentialProviders error")
225+
}
226+
}
227+
_, err = c.Get()
223228
if awsErr, ok := err.(awserr.Error); ok {
224229
if awsErr.Code() != "NoCredentialProviders" {
225230
t.Fatalf("Expected NoCredentialProviders error")
@@ -251,10 +256,13 @@ func TestAWSGetCredentials_shouldBeStatic(t *testing.T) {
251256
Token: c.Token,
252257
}
253258

254-
creds := GetCredentials(&cfg)
259+
creds, err := GetCredentials(&cfg)
255260
if creds == nil {
256261
t.Fatalf("Expected a static creds provider to be returned")
257262
}
263+
if err != nil {
264+
t.Fatalf("Error gettings creds: %s", err)
265+
}
258266
v, err := creds.Get()
259267
if err != nil {
260268
t.Fatalf("Error gettings creds: %s", err)
@@ -286,11 +294,13 @@ func TestAWSGetCredentials_shouldIAM(t *testing.T) {
286294
// An empty config, no key supplied
287295
cfg := Config{}
288296

289-
creds := GetCredentials(&cfg)
297+
creds, err := GetCredentials(&cfg)
290298
if creds == nil {
291299
t.Fatalf("Expected a static creds provider to be returned")
292300
}
293-
301+
if err != nil {
302+
t.Fatalf("Error gettings creds: %s", err)
303+
}
294304
v, err := creds.Get()
295305
if err != nil {
296306
t.Fatalf("Error gettings creds: %s", err)
@@ -335,10 +345,13 @@ func TestAWSGetCredentials_shouldIgnoreIAM(t *testing.T) {
335345
Token: c.Token,
336346
}
337347

338-
creds := GetCredentials(&cfg)
348+
creds, err := GetCredentials(&cfg)
339349
if creds == nil {
340350
t.Fatalf("Expected a static creds provider to be returned")
341351
}
352+
if err != nil {
353+
t.Fatalf("Error gettings creds: %s", err)
354+
}
342355
v, err := creds.Get()
343356
if err != nil {
344357
t.Fatalf("Error gettings creds: %s", err)
@@ -362,7 +375,10 @@ func TestAWSGetCredentials_shouldErrorWithInvalidEndpoint(t *testing.T) {
362375
ts := invalidAwsEnv(t)
363376
defer ts()
364377

365-
creds := GetCredentials(&Config{})
378+
creds, err := GetCredentials(&Config{})
379+
if err != nil {
380+
t.Fatalf("Error gettings creds: %s", err)
381+
}
366382
v, err := creds.Get()
367383
if err == nil {
368384
t.Fatal("Expected error returned when getting creds w/ invalid EC2 endpoint")
@@ -380,7 +396,10 @@ func TestAWSGetCredentials_shouldIgnoreInvalidEndpoint(t *testing.T) {
380396
ts := invalidAwsEnv(t)
381397
defer ts()
382398

383-
creds := GetCredentials(&Config{AccessKey: "accessKey", SecretKey: "secretKey"})
399+
creds, err := GetCredentials(&Config{AccessKey: "accessKey", SecretKey: "secretKey"})
400+
if err != nil {
401+
t.Fatalf("Error gettings creds: %s", err)
402+
}
384403
v, err := creds.Get()
385404
if err != nil {
386405
t.Fatalf("Getting static credentials w/ invalid EC2 endpoint failed: %s", err)
@@ -406,10 +425,13 @@ func TestAWSGetCredentials_shouldCatchEC2RoleProvider(t *testing.T) {
406425
ts := awsEnv(t)
407426
defer ts()
408427

409-
creds := GetCredentials(&Config{})
428+
creds, err := GetCredentials(&Config{})
410429
if creds == nil {
411430
t.Fatalf("Expected an EC2Role creds provider to be returned")
412431
}
432+
if err != nil {
433+
t.Fatalf("Error gettings creds: %s", err)
434+
}
413435
v, err := creds.Get()
414436
if err != nil {
415437
t.Fatalf("Expected no error when getting creds: %s", err)
@@ -452,10 +474,13 @@ func TestAWSGetCredentials_shouldBeShared(t *testing.T) {
452474
t.Fatalf("Error resetting env var AWS_SHARED_CREDENTIALS_FILE: %s", err)
453475
}
454476

455-
creds := GetCredentials(&Config{Profile: "myprofile", CredsFilename: file.Name()})
477+
creds, err := GetCredentials(&Config{Profile: "myprofile", CredsFilename: file.Name()})
456478
if creds == nil {
457479
t.Fatalf("Expected a provider chain to be returned")
458480
}
481+
if err != nil {
482+
t.Fatalf("Error gettings creds: %s", err)
483+
}
459484
v, err := creds.Get()
460485
if err != nil {
461486
t.Fatalf("Error gettings creds: %s", err)
@@ -479,10 +504,13 @@ func TestAWSGetCredentials_shouldBeENV(t *testing.T) {
479504
defer resetEnv()
480505

481506
cfg := Config{}
482-
creds := GetCredentials(&cfg)
507+
creds, err := GetCredentials(&cfg)
483508
if creds == nil {
484509
t.Fatalf("Expected a static creds provider to be returned")
485510
}
511+
if err != nil {
512+
t.Fatalf("Error gettings creds: %s", err)
513+
}
486514
v, err := creds.Get()
487515
if err != nil {
488516
t.Fatalf("Error gettings creds: %s", err)

builtin/providers/aws/config.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ type Config struct {
6666
Profile string
6767
Token string
6868
Region string
69+
RoleArn string
6970
MaxRetries int
7071

7172
AllowedAccountIds []interface{}
@@ -150,7 +151,10 @@ func (c *Config) Client() (interface{}, error) {
150151
client.region = c.Region
151152

152153
log.Println("[INFO] Building AWS auth structure")
153-
creds := GetCredentials(c)
154+
creds, err := GetCredentials(c)
155+
if err != nil {
156+
return nil, &multierror.Error{Errors: errs}
157+
}
154158
// Call Get to check for credential provider. If nothing found, we'll get an
155159
// error, and we can present it nicely to the user
156160
cp, err := creds.Get()

builtin/providers/aws/provider.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ func Provider() terraform.ResourceProvider {
6464
InputDefault: "us-east-1",
6565
},
6666

67+
"role_arn": &schema.Schema{
68+
Type: schema.TypeString,
69+
Optional: true,
70+
Default: "",
71+
Description: descriptions["role_arn"],
72+
},
73+
6774
"max_retries": &schema.Schema{
6875
Type: schema.TypeInt,
6976
Optional: true,
@@ -353,6 +360,8 @@ func init() {
353360
"profile": "The profile for API operations. If not set, the default profile\n" +
354361
"created with `aws configure` will be used.",
355362

363+
"role_arn": "The role to be assumed using the supplied access_key and secret_key",
364+
356365
"shared_credentials_file": "The path to the shared credentials file. If not set\n" +
357366
"this defaults to ~/.aws/credentials.",
358367

@@ -404,6 +413,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
404413
CredsFilename: d.Get("shared_credentials_file").(string),
405414
Token: d.Get("token").(string),
406415
Region: d.Get("region").(string),
416+
RoleArn: d.Get("role_arn").(string),
407417
MaxRetries: d.Get("max_retries").(int),
408418
DynamoDBEndpoint: d.Get("dynamodb_endpoint").(string),
409419
KinesisEndpoint: d.Get("kinesis_endpoint").(string),

state/remote/s3.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func s3Factory(conf map[string]string) (Client, error) {
6060
kmsKeyID := conf["kms_key_id"]
6161

6262
var errs []error
63-
creds := terraformAws.GetCredentials(&terraformAws.Config{
63+
creds, err := terraformAws.GetCredentials(&terraformAws.Config{
6464
AccessKey: conf["access_key"],
6565
SecretKey: conf["secret_key"],
6666
Token: conf["token"],
@@ -69,7 +69,7 @@ func s3Factory(conf map[string]string) (Client, error) {
6969
})
7070
// Call Get to check for credential provider. If nothing found, we'll get an
7171
// error, and we can present it nicely to the user
72-
_, err := creds.Get()
72+
_, err = creds.Get()
7373
if err != nil {
7474
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
7575
errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS S3 remote.

website/source/docs/providers/aws/index.html.markdown

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ You can provide custom metadata API endpoint via `AWS_METADATA_ENDPOINT` variabl
111111
which expects the endpoint URL including the version
112112
and defaults to `http://169.254.169.254:80/latest`.
113113

114+
###Assume role
115+
116+
If provided with a role arn, terraform will attempt to assume this role
117+
using the supplied credentials.
118+
119+
Usage:
120+
121+
```
122+
provider "aws" {
123+
role_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
124+
}
125+
```
126+
114127
## Argument Reference
115128

116129
The following arguments are supported in the `provider` block:

0 commit comments

Comments
 (0)