Browse Source

:sparkles: AWS Role Chaining (#1855)

Signed-off-by: cspargo <colinspargo@gmail.com>
cspargo 3 years ago
parent
commit
fdc21faf61

+ 4 - 0
apis/externalsecrets/v1beta1/secretstore_aws_types.go

@@ -79,4 +79,8 @@ type AWSProvider struct {
 
 
 	// AWS Region to be used for the provider
 	// AWS Region to be used for the provider
 	Region string `json:"region"`
 	Region string `json:"region"`
+
+	// AdditionalRoles is a chained list of Role ARNs which the SecretManager provider will sequentially assume before assuming Role
+	// +optional
+	AdditionalRoles []string `json:"additionalRoles,omitempty"`
 }
 }

+ 5 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -96,6 +96,11 @@ func (in *AWSJWTAuth) DeepCopy() *AWSJWTAuth {
 func (in *AWSProvider) DeepCopyInto(out *AWSProvider) {
 func (in *AWSProvider) DeepCopyInto(out *AWSProvider) {
 	*out = *in
 	*out = *in
 	in.Auth.DeepCopyInto(&out.Auth)
 	in.Auth.DeepCopyInto(&out.Auth)
+	if in.AdditionalRoles != nil {
+		in, out := &in.AdditionalRoles, &out.AdditionalRoles
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 }
 }
 
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSProvider.
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSProvider.

+ 7 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -1837,6 +1837,13 @@ spec:
                     description: AWS configures this store to sync secrets using AWS
                     description: AWS configures this store to sync secrets using AWS
                       Secret Manager provider
                       Secret Manager provider
                     properties:
                     properties:
+                      additionalRoles:
+                        description: AdditionalRoles is a chained list of Role ARNs
+                          which the SecretManager provider will sequentially assume
+                          before assuming Role
+                        items:
+                          type: string
+                        type: array
                       auth:
                       auth:
                         description: 'Auth defines the information necessary to authenticate
                         description: 'Auth defines the information necessary to authenticate
                           against AWS if not set aws sdk will infer credentials from
                           against AWS if not set aws sdk will infer credentials from

+ 7 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -1837,6 +1837,13 @@ spec:
                     description: AWS configures this store to sync secrets using AWS
                     description: AWS configures this store to sync secrets using AWS
                       Secret Manager provider
                       Secret Manager provider
                     properties:
                     properties:
+                      additionalRoles:
+                        description: AdditionalRoles is a chained list of Role ARNs
+                          which the SecretManager provider will sequentially assume
+                          before assuming Role
+                        items:
+                          type: string
+                        type: array
                       auth:
                       auth:
                         description: 'Auth defines the information necessary to authenticate
                         description: 'Auth defines the information necessary to authenticate
                           against AWS if not set aws sdk will infer credentials from
                           against AWS if not set aws sdk will infer credentials from

+ 10 - 0
deploy/crds/bundle.yaml

@@ -1781,6 +1781,11 @@ spec:
                     aws:
                     aws:
                       description: AWS configures this store to sync secrets using AWS Secret Manager provider
                       description: AWS configures this store to sync secrets using AWS Secret Manager provider
                       properties:
                       properties:
+                        additionalRoles:
+                          description: AdditionalRoles is a chained list of Role ARNs which the SecretManager provider will sequentially assume before assuming Role
+                          items:
+                            type: string
+                          type: array
                         auth:
                         auth:
                           description: 'Auth defines the information necessary to authenticate against AWS if not set aws sdk will infer credentials from your environment see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials'
                           description: 'Auth defines the information necessary to authenticate against AWS if not set aws sdk will infer credentials from your environment see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials'
                           properties:
                           properties:
@@ -5069,6 +5074,11 @@ spec:
                     aws:
                     aws:
                       description: AWS configures this store to sync secrets using AWS Secret Manager provider
                       description: AWS configures this store to sync secrets using AWS Secret Manager provider
                       properties:
                       properties:
+                        additionalRoles:
+                          description: AdditionalRoles is a chained list of Role ARNs which the SecretManager provider will sequentially assume before assuming Role
+                          items:
+                            type: string
+                          type: array
                         auth:
                         auth:
                           description: 'Auth defines the information necessary to authenticate against AWS if not set aws sdk will infer credentials from your environment see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials'
                           description: 'Auth defines the information necessary to authenticate against AWS if not set aws sdk will infer credentials from your environment see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials'
                           properties:
                           properties:

+ 12 - 0
docs/api/spec.md

@@ -218,6 +218,18 @@ string
 <p>AWS Region to be used for the provider</p>
 <p>AWS Region to be used for the provider</p>
 </td>
 </td>
 </tr>
 </tr>
+<tr>
+<td>
+<code>additionalRoles</code></br>
+<em>
+[]string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>AdditionalRoles is a chained list of Role ARNs which the SecretManager provider will sequentially assume before assuming Role</p>
+</td>
+</tr>
 </tbody>
 </tbody>
 </table>
 </table>
 <h3 id="external-secrets.io/v1beta1.AWSServiceType">AWSServiceType
 <h3 id="external-secrets.io/v1beta1.AWSServiceType">AWSServiceType

+ 5 - 0
pkg/provider/aws/auth/auth.go

@@ -116,6 +116,11 @@ func New(ctx context.Context, store esv1beta1.GenericStore, kube client.Client,
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	for _, aRole := range prov.AdditionalRoles {
+		stsclient := assumeRoler(sess)
+		sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, aRole))
+	}
+
 	if prov.Role != "" {
 	if prov.Role != "" {
 		stsclient := assumeRoler(sess)
 		stsclient := assumeRoler(sess)
 		sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, prov.Role))
 		sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, prov.Role))

+ 57 - 17
pkg/provider/aws/auth/auth_test.go

@@ -476,20 +476,48 @@ func TestSMAssumeRole(t *testing.T) {
 	k8sClient := clientfake.NewClientBuilder().Build()
 	k8sClient := clientfake.NewClientBuilder().Build()
 	sts := &fakesess.AssumeRoler{
 	sts := &fakesess.AssumeRoler{
 		AssumeRoleFunc: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
 		AssumeRoleFunc: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
-			// make sure the correct role is passed in
-			assert.Equal(t, *input.RoleArn, "my-awesome-role")
-			return &sts.AssumeRoleOutput{
-				AssumedRoleUser: &sts.AssumedRoleUser{
-					Arn:           aws.String("1123132"),
-					AssumedRoleId: aws.String("xxxxx"),
-				},
-				Credentials: &sts.Credentials{
-					AccessKeyId:     aws.String("3333"),
-					SecretAccessKey: aws.String("4444"),
-					Expiration:      aws.Time(time.Now().Add(time.Hour)),
-					SessionToken:    aws.String("6666"),
-				},
-			}, nil
+			if *input.RoleArn == "chained-role-1" {
+				return &sts.AssumeRoleOutput{
+					AssumedRoleUser: &sts.AssumedRoleUser{
+						Arn:           aws.String("1111111"),
+						AssumedRoleId: aws.String("yyyyy1"),
+					},
+					Credentials: &sts.Credentials{
+						AccessKeyId:     aws.String("77771"),
+						SecretAccessKey: aws.String("88881"),
+						Expiration:      aws.Time(time.Now().Add(time.Hour)),
+						SessionToken:    aws.String("99991"),
+					},
+				}, nil
+			} else if *input.RoleArn == "chained-role-2" {
+				return &sts.AssumeRoleOutput{
+					AssumedRoleUser: &sts.AssumedRoleUser{
+						Arn:           aws.String("2222222"),
+						AssumedRoleId: aws.String("yyyyy2"),
+					},
+					Credentials: &sts.Credentials{
+						AccessKeyId:     aws.String("77772"),
+						SecretAccessKey: aws.String("88882"),
+						Expiration:      aws.Time(time.Now().Add(time.Hour)),
+						SessionToken:    aws.String("99992"),
+					},
+				}, nil
+			} else {
+				// make sure the correct role is passed in
+				assert.Equal(t, *input.RoleArn, "my-awesome-role")
+				return &sts.AssumeRoleOutput{
+					AssumedRoleUser: &sts.AssumedRoleUser{
+						Arn:           aws.String("1123132"),
+						AssumedRoleId: aws.String("xxxxx"),
+					},
+					Credentials: &sts.Credentials{
+						AccessKeyId:     aws.String("3333"),
+						SecretAccessKey: aws.String("4444"),
+						Expiration:      aws.Time(time.Now().Add(time.Hour)),
+						SessionToken:    aws.String("6666"),
+					},
+				}, nil
+			}
 		},
 		},
 	}
 	}
 	t.Setenv("AWS_SECRET_ACCESS_KEY", "1111")
 	t.Setenv("AWS_SECRET_ACCESS_KEY", "1111")
@@ -499,7 +527,8 @@ func TestSMAssumeRole(t *testing.T) {
 			Provider: &esv1beta1.SecretStoreProvider{
 			Provider: &esv1beta1.SecretStoreProvider{
 				// do assume role!
 				// do assume role!
 				AWS: &esv1beta1.AWSProvider{
 				AWS: &esv1beta1.AWSProvider{
-					Role: "my-awesome-role",
+					Role:            "my-awesome-role",
+					AdditionalRoles: []string{"chained-role-1", "chained-role-2"},
 				},
 				},
 			},
 			},
 		},
 		},
@@ -507,8 +536,19 @@ func TestSMAssumeRole(t *testing.T) {
 		// check if the correct temporary credentials were used
 		// check if the correct temporary credentials were used
 		creds, err := se.Config.Credentials.Get()
 		creds, err := se.Config.Credentials.Get()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
-		assert.Equal(t, creds.AccessKeyID, "2222")
-		assert.Equal(t, creds.SecretAccessKey, "1111")
+		if creds.SessionToken == "" {
+			// called with credentials from envvars
+			assert.Equal(t, creds.AccessKeyID, "2222")
+			assert.Equal(t, creds.SecretAccessKey, "1111")
+		} else if creds.SessionToken == "99991" {
+			// called with chained role 1's credentials
+			assert.Equal(t, creds.AccessKeyID, "77771")
+			assert.Equal(t, creds.SecretAccessKey, "88881")
+		} else {
+			// called with chained role 2's credentials
+			assert.Equal(t, creds.AccessKeyID, "77772")
+			assert.Equal(t, creds.SecretAccessKey, "88882")
+		}
 		return sts
 		return sts
 	}, nil)
 	}, nil)
 	assert.Nil(t, err)
 	assert.Nil(t, err)