Skip to content

Commit bcc23c4

Browse files
committed
Fetch credentials from provider block
1 parent 5621d42 commit bcc23c4

File tree

6 files changed

+368
-11
lines changed

6 files changed

+368
-11
lines changed

aws/client.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,35 @@ func formatBaseConfigError(err error) error {
107107
}
108108
return err
109109
}
110+
111+
// Merge returns a merged credentials
112+
func (c Credentials) Merge(other Credentials) Credentials {
113+
if other.AccessKey != "" {
114+
c.AccessKey = other.AccessKey
115+
}
116+
if other.SecretKey != "" {
117+
c.SecretKey = other.SecretKey
118+
}
119+
if other.Profile != "" {
120+
c.Profile = other.Profile
121+
}
122+
if other.CredsFile != "" {
123+
c.CredsFile = other.CredsFile
124+
}
125+
if other.Region != "" {
126+
c.Region = other.Region
127+
}
128+
if other.AssumeRoleARN != "" {
129+
c.AssumeRoleARN = other.AssumeRoleARN
130+
}
131+
if other.AssumeRoleSessionName != "" {
132+
c.AssumeRoleSessionName = other.AssumeRoleSessionName
133+
}
134+
if other.AssumeRoleExternalID != "" {
135+
c.AssumeRoleExternalID = other.AssumeRoleExternalID
136+
}
137+
if other.AssumeRolePolicy != "" {
138+
c.AssumeRolePolicy = other.AssumeRolePolicy
139+
}
140+
return c
141+
}

aws/client_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,108 @@ func Test_getBaseConfig(t *testing.T) {
5858
}
5959
}
6060
}
61+
62+
func Test_Merge(t *testing.T) {
63+
cases := []struct {
64+
Name string
65+
Self Credentials
66+
Other Credentials
67+
Expected Credentials
68+
}{
69+
{
70+
Name: "self is empty",
71+
Self: Credentials{},
72+
Other: Credentials{
73+
AccessKey: "AWS_ACCESS_KEY",
74+
SecretKey: "AWS_SECRET_KEY",
75+
Profile: "default",
76+
CredsFile: "~/.aws/creds",
77+
AssumeRoleARN: "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME",
78+
AssumeRoleSessionName: "SESSION_NAME",
79+
AssumeRoleExternalID: "EXTERNAL_ID",
80+
AssumeRolePolicy: "POLICY_NAME",
81+
Region: "us-east-1",
82+
},
83+
Expected: Credentials{
84+
AccessKey: "AWS_ACCESS_KEY",
85+
SecretKey: "AWS_SECRET_KEY",
86+
Profile: "default",
87+
CredsFile: "~/.aws/creds",
88+
AssumeRoleARN: "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME",
89+
AssumeRoleSessionName: "SESSION_NAME",
90+
AssumeRoleExternalID: "EXTERNAL_ID",
91+
AssumeRolePolicy: "POLICY_NAME",
92+
Region: "us-east-1",
93+
},
94+
},
95+
{
96+
Name: "other is empty",
97+
Self: Credentials{
98+
AccessKey: "AWS_ACCESS_KEY_2",
99+
SecretKey: "AWS_SECRET_KEY_2",
100+
Profile: "staging",
101+
CredsFile: "~/.aws/creds_stg",
102+
AssumeRoleARN: "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME",
103+
AssumeRoleSessionName: "SESSION_NAME",
104+
AssumeRoleExternalID: "EXTERNAL_ID",
105+
AssumeRolePolicy: "POLICY_NAME",
106+
Region: "ap-northeast-1",
107+
},
108+
Other: Credentials{},
109+
Expected: Credentials{
110+
AccessKey: "AWS_ACCESS_KEY_2",
111+
SecretKey: "AWS_SECRET_KEY_2",
112+
Profile: "staging",
113+
CredsFile: "~/.aws/creds_stg",
114+
AssumeRoleARN: "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME",
115+
AssumeRoleSessionName: "SESSION_NAME",
116+
AssumeRoleExternalID: "EXTERNAL_ID",
117+
AssumeRolePolicy: "POLICY_NAME",
118+
Region: "ap-northeast-1",
119+
},
120+
},
121+
{
122+
Name: "merged",
123+
Self: Credentials{
124+
AccessKey: "AWS_ACCESS_KEY_2",
125+
SecretKey: "AWS_SECRET_KEY_2",
126+
Profile: "staging",
127+
CredsFile: "~/.aws/creds_stg",
128+
AssumeRoleARN: "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME_2",
129+
AssumeRoleSessionName: "SESSION_NAME_2",
130+
AssumeRoleExternalID: "EXTERNAL_ID_2",
131+
AssumeRolePolicy: "POLICY_NAME_2",
132+
Region: "ap-northeast-1",
133+
},
134+
Other: Credentials{
135+
AccessKey: "AWS_ACCESS_KEY",
136+
SecretKey: "AWS_SECRET_KEY",
137+
Profile: "default",
138+
CredsFile: "~/.aws/creds",
139+
AssumeRoleARN: "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME",
140+
AssumeRoleSessionName: "SESSION_NAME",
141+
AssumeRoleExternalID: "EXTERNAL_ID",
142+
AssumeRolePolicy: "POLICY_NAME",
143+
Region: "us-east-1",
144+
},
145+
Expected: Credentials{
146+
AccessKey: "AWS_ACCESS_KEY",
147+
SecretKey: "AWS_SECRET_KEY",
148+
Profile: "default",
149+
CredsFile: "~/.aws/creds",
150+
AssumeRoleARN: "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME",
151+
AssumeRoleSessionName: "SESSION_NAME",
152+
AssumeRoleExternalID: "EXTERNAL_ID",
153+
AssumeRolePolicy: "POLICY_NAME",
154+
Region: "us-east-1",
155+
},
156+
},
157+
}
158+
159+
for _, tc := range cases {
160+
ret := tc.Self.Merge(tc.Other)
161+
if !cmp.Equal(tc.Expected, ret) {
162+
t.Fatalf("Failed `%s` test: Diff=%s", tc.Name, cmp.Diff(tc.Expected, ret))
163+
}
164+
}
165+
}

aws/provider.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package aws
2+
3+
import (
4+
"log"
5+
6+
"github.com/hashicorp/hcl/v2"
7+
"github.com/terraform-linters/tflint-plugin-sdk/terraform/configs"
8+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
9+
)
10+
11+
// AwsProviderBlockSchema is a schema of `aws` provider block
12+
var AwsProviderBlockSchema = &hcl.BodySchema{
13+
Attributes: []hcl.AttributeSchema{
14+
{Name: "access_key"},
15+
{Name: "secret_key"},
16+
{Name: "profile"},
17+
{Name: "shared_credentials_file"},
18+
{Name: "region"},
19+
},
20+
Blocks: []hcl.BlockHeaderSchema{
21+
{Type: "assume_role"},
22+
},
23+
}
24+
25+
// AwsProviderAssumeRoleBlockShema is a schema of `assume_role` block
26+
var AwsProviderAssumeRoleBlockShema = &hcl.BodySchema{
27+
Attributes: []hcl.AttributeSchema{
28+
{Name: "role_arn", Required: true},
29+
{Name: "session_name"},
30+
{Name: "external_id"},
31+
{Name: "policy"},
32+
},
33+
}
34+
35+
// ProviderData represents a provider block with an eval context (runner)
36+
type ProviderData struct {
37+
provider *configs.Provider
38+
runner tflint.Runner
39+
attributes hcl.Attributes
40+
blocks hcl.Blocks
41+
}
42+
43+
// GetCredentialsFromProvider retrieves credentials from the "provider" block in the Terraform configuration
44+
func GetCredentialsFromProvider(runner tflint.Runner) (Credentials, error) {
45+
creds := Credentials{}
46+
47+
providerConfig, err := runner.RootProvider("aws")
48+
if err != nil || providerConfig == nil {
49+
return creds, err
50+
}
51+
52+
d, err := newProviderData(providerConfig, runner)
53+
if err != nil {
54+
return creds, err
55+
}
56+
57+
accessKey, exists, err := d.Get("access_key")
58+
if err != nil {
59+
return creds, err
60+
}
61+
if exists {
62+
creds.AccessKey = accessKey
63+
}
64+
65+
secretKey, exists, err := d.Get("secret_key")
66+
if err != nil {
67+
return creds, err
68+
}
69+
if exists {
70+
creds.SecretKey = secretKey
71+
}
72+
73+
profile, exists, err := d.Get("profile")
74+
if err != nil {
75+
return creds, err
76+
}
77+
if exists {
78+
creds.Profile = profile
79+
}
80+
81+
credsFile, exists, err := d.Get("shared_credentials_file")
82+
if err != nil {
83+
return creds, err
84+
}
85+
if exists {
86+
creds.CredsFile = credsFile
87+
}
88+
89+
region, exists, err := d.Get("region")
90+
if err != nil {
91+
return creds, err
92+
}
93+
if exists {
94+
creds.Region = region
95+
}
96+
97+
assumeRole, exists, err := d.GetBlock("assume_role", AwsProviderAssumeRoleBlockShema)
98+
if err != nil {
99+
return creds, err
100+
}
101+
if exists {
102+
roleARN, exists, err := assumeRole.Get("role_arn")
103+
if err != nil {
104+
return creds, err
105+
}
106+
if exists {
107+
creds.AssumeRoleARN = roleARN
108+
}
109+
110+
sessionName, exists, err := assumeRole.Get("session_name")
111+
if err != nil {
112+
return creds, err
113+
}
114+
if exists {
115+
creds.AssumeRoleSessionName = sessionName
116+
}
117+
118+
externalID, exists, err := assumeRole.Get("external_id")
119+
if err != nil {
120+
return creds, err
121+
}
122+
if exists {
123+
creds.AssumeRoleExternalID = externalID
124+
}
125+
126+
policy, exists, err := assumeRole.Get("policy")
127+
if err != nil {
128+
return creds, err
129+
}
130+
if exists {
131+
creds.AssumeRolePolicy = policy
132+
}
133+
}
134+
135+
return creds, nil
136+
}
137+
138+
func newProviderData(provider *configs.Provider, runner tflint.Runner) (*ProviderData, error) {
139+
providerData := &ProviderData{
140+
provider: provider,
141+
runner: runner,
142+
attributes: map[string]*hcl.Attribute{},
143+
blocks: []*hcl.Block{},
144+
}
145+
146+
if provider != nil {
147+
content, _, diags := provider.Config.PartialContent(AwsProviderBlockSchema)
148+
if diags.HasErrors() {
149+
return nil, diags
150+
}
151+
152+
providerData.attributes = content.Attributes
153+
providerData.blocks = content.Blocks
154+
}
155+
156+
return providerData, nil
157+
}
158+
159+
// Get returns a value corresponding to the given key
160+
// It should be noted that the value is evaluated if it is evaluable
161+
// The second return value is a flag that determines whether a value exists
162+
// We assume the provider has only simple attributes, so it just returns string
163+
func (d *ProviderData) Get(key string) (string, bool, error) {
164+
attribute, exists := d.attributes[key]
165+
if !exists {
166+
log.Printf("[INFO] `%s` is not found in the provider block.", key)
167+
return "", false, nil
168+
}
169+
170+
var val string
171+
err := d.runner.EvaluateExprOnRootCtx(attribute.Expr, &val)
172+
173+
err = d.runner.EnsureNoError(err, func() error { return nil })
174+
if err != nil {
175+
return "", true, err
176+
}
177+
return val, true, nil
178+
}
179+
180+
// GetBlock returns a value just like Get.
181+
// The difference is that GetBlock returns ProviderData rather than a string value.
182+
func (d *ProviderData) GetBlock(key string, schema *hcl.BodySchema) (*ProviderData, bool, error) {
183+
providerData := &ProviderData{
184+
provider: d.provider,
185+
runner: d.runner,
186+
attributes: map[string]*hcl.Attribute{},
187+
blocks: []*hcl.Block{},
188+
}
189+
190+
var ret *hcl.Block
191+
for _, block := range d.blocks {
192+
if block.Type == key {
193+
ret = block
194+
}
195+
}
196+
if ret == nil {
197+
log.Printf("[INFO] `%s` is not found in the provider block.", key)
198+
return providerData, false, nil
199+
}
200+
201+
content, _, diags := ret.Body.PartialContent(schema)
202+
if diags.HasErrors() {
203+
return providerData, true, diags
204+
}
205+
206+
providerData.attributes = content.Attributes
207+
providerData.blocks = content.Blocks
208+
209+
return providerData, true, nil
210+
}

aws/runner.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ type Runner struct {
1919
func NewRunner(runner tflint.Runner, config *Config) (*Runner, error) {
2020
var client *Client
2121
if config.DeepCheck {
22-
var err error
23-
client, err = NewClient(config.toCredentials())
22+
credentials, err := GetCredentialsFromProvider(runner)
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
client, err = NewClient(config.toCredentials().Merge(credentials))
2428
if err != nil {
2529
return nil, err
2630
}

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ go 1.15
55
require (
66
github.com/aws/aws-sdk-go v1.35.2
77
github.com/golang/mock v1.4.3
8-
github.com/google/go-cmp v0.5.2
8+
github.com/google/go-cmp v0.5.3
99
github.com/hashicorp/aws-sdk-go-base v0.7.0
10-
github.com/hashicorp/hcl/v2 v2.7.0
10+
github.com/hashicorp/hcl/v2 v2.7.1
1111
github.com/hashicorp/terraform-plugin-sdk/v2 v2.2.0
1212
github.com/mitchellh/go-homedir v1.1.0
1313
github.com/onsi/ginkgo v1.14.2 // indirect
1414
github.com/onsi/gomega v1.10.3 // indirect
1515
github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e
16-
github.com/terraform-linters/tflint-plugin-sdk v0.5.1-0.20201116165411-717d11fa62f2
16+
github.com/terraform-linters/tflint-plugin-sdk v0.6.1-0.20201205142940-d49361e1c42c
1717
github.com/terraform-providers/terraform-provider-aws v1.60.1-0.20201015205411-546f68d4a935
1818
)

0 commit comments

Comments
 (0)