Skip to content

Commit

Permalink
Make the AWS session token duration a configurable setting (#1067)
Browse files Browse the repository at this point in the history
* Add assume role duration field to ExtractAWSCredentials.
Update callers of the function to pass default duration.
Update GetCredentials, so that it accepts the configured assume role duration instead
of the previous hard coded default.

* Added log for debugging purposes.

* Set the Duration field of the WebIdentityProvider object to control
the duration of the session token.

* Log expiration time.
Enable debug logging in AWS SDK

* Removed an info log.
Updated an info log to debug log to print the expiration time..

* Remove aws sdk log level config.
Add back log with assumeRoleDuration. But made it a debug log instead of info log.

* Update pkg/secrets/aws_test.go

Co-authored-by: Pavan Navarathna <pavan@kasten.io>

* Update comments for ExtractAWSCredentials.

* Fix a comment.

* Added helper function for getting creds with duration.
Added comments for the helper function.

Co-authored-by: Pavan Navarathna <pavan@kasten.io>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Aug 12, 2021
1 parent ea824b0 commit 68e326e
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 16 deletions.
36 changes: 31 additions & 5 deletions pkg/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/pkg/errors"

awsrole "github.com/kanisterio/kanister/pkg/aws/role"
"github.com/kanisterio/kanister/pkg/field"
"github.com/kanisterio/kanister/pkg/log"
)

const (
Expand All @@ -49,14 +52,27 @@ const (
roleARNEnvKey = "AWS_ROLE_ARN"

// TODO: Make this configurable via `config`
assumeRoleDurationDefault = 90 * time.Minute
AssumeRoleDurationDefault = 90 * time.Minute
AssumeRoleDuration = "assumeRoleDuration"
)

func durationFromString(config map[string]string) (time.Duration, error) {
d, ok := config[AssumeRoleDuration]
if !ok || d == "" {
return AssumeRoleDurationDefault, nil
}
return time.ParseDuration(d)
}

// GetCredentials returns credentials to use for AWS operations
func GetCredentials(ctx context.Context, config map[string]string) (*credentials.Credentials, error) {
var creds *credentials.Credentials
var assumedRole string
assumeRoleDuration := assumeRoleDurationDefault
assumeRoleDuration, err := durationFromString(config)
if err != nil {
return nil, errors.Wrap(err, "Failed to get assume role duration")
}
log.Debug().Print("Assume Role Duration setup", field.M{"assumeRoleDuration": assumeRoleDuration})
switch {
case config[AccessKeyID] != "" && config[SecretAccessKey] != "":
// If AccessKeys were provided - use those
Expand All @@ -66,8 +82,7 @@ func GetCredentials(ctx context.Context, config map[string]string) (*credentials
if err != nil {
return nil, errors.Wrap(err, "Failed to create session to initialize Web Identify credentials")
}
// If we have credentials to use with a Web Identity provider - use those
creds = stscreds.NewWebIdentityCredentials(sess, os.Getenv(roleARNEnvKey), "", os.Getenv(webIdentityTokenFilePathEnvKey))
creds = getCredentialsWithDuration(sess, assumeRoleDuration)
assumedRole = os.Getenv(roleARNEnvKey)
default:
return nil, errors.New("AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY required to initialize AWS credentials")
Expand All @@ -84,10 +99,21 @@ func GetCredentials(ctx context.Context, config map[string]string) (*credentials

// If the caller wants to use a specific role, use the credentials initialized above to assume that
// role and return those credentials instead
creds, err := awsrole.Switch(ctx, creds, config[ConfigRole], assumeRoleDuration)
creds, err = awsrole.Switch(ctx, creds, config[ConfigRole], assumeRoleDuration)
return creds, errors.Wrap(err, "Failed to switch roles")
}

// getCredentialsWithDuration returns credentials with the given duration.
// In order to set a custom assume role duration, we have to get the
// the provider first and then set it's Duration field before
// getting the credentials from the provider.
func getCredentialsWithDuration(sess *session.Session, duration time.Duration) *credentials.Credentials {
svc := sts.New(sess)
p := stscreds.NewWebIdentityRoleProvider(svc, os.Getenv(roleARNEnvKey), "", os.Getenv(webIdentityTokenFilePathEnvKey))
p.Duration = duration
return credentials.NewCredentials(p)
}

// GetConfig returns a configuration to establish AWS connection and connected region name.
func GetConfig(ctx context.Context, config map[string]string) (awsConfig *aws.Config, region string, err error) {
region, ok := config[ConfigRegion]
Expand Down
3 changes: 2 additions & 1 deletion pkg/location/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/pkg/errors"

crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
"github.com/kanisterio/kanister/pkg/aws"
"github.com/kanisterio/kanister/pkg/objectstore"
"github.com/kanisterio/kanister/pkg/param"
"github.com/kanisterio/kanister/pkg/secrets"
Expand Down Expand Up @@ -229,7 +230,7 @@ func getAWSSecret(ctx context.Context, cred param.Credential) (*objectstore.Secr
}
return os, nil
case param.CredentialTypeSecret:
creds, err := secrets.ExtractAWSCredentials(ctx, cred.Secret)
creds, err := secrets.ExtractAWSCredentials(ctx, cred.Secret, aws.AssumeRoleDurationDefault)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/restic/restic.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"k8s.io/client-go/kubernetes"

crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
"github.com/kanisterio/kanister/pkg/aws"
"github.com/kanisterio/kanister/pkg/consts"
"github.com/kanisterio/kanister/pkg/format"
"github.com/kanisterio/kanister/pkg/kube"
Expand Down Expand Up @@ -235,7 +236,7 @@ func resticS3CredentialArgs(creds param.Credential) ([]string, error) {
}

func resticS3CredentialSecretArgs(secret *v1.Secret) ([]string, error) {
creds, err := secrets.ExtractAWSCredentials(context.Background(), secret)
creds, err := secrets.ExtractAWSCredentials(context.Background(), secret, aws.AssumeRoleDurationDefault)
if err != nil {
return nil, err
}
Expand Down
25 changes: 21 additions & 4 deletions pkg/secrets/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package secrets

import (
"context"
"time"

"github.com/aws/aws-sdk-go/aws/credentials"
v1 "k8s.io/api/core/v1"

"github.com/kanisterio/kanister/pkg/aws"
"github.com/kanisterio/kanister/pkg/field"
"github.com/kanisterio/kanister/pkg/log"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -64,14 +67,24 @@ func ValidateAWSCredentials(secret *v1.Secret) error {
//
// If the type of the secret is not "secret.kanister.io/aws", it returns an error.
// If the required types are not avaialable in the secrets, it returns an errror.
func ExtractAWSCredentials(ctx context.Context, secret *v1.Secret) (*credentials.Value, error) {
//
// ExtractAWSCredentials accepts an assumeRoleDuration which is used to set
// the duration of the AWS session token.
// When this setting is not provided, the default duration of a token is 1h.
// The minimum value allowed is 15 minutes (15m).
// The maximum value depends on the max duration setting
// of the IAM role - The setting can be viewed using instructions here
// https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html#id_roles_use_view-role-max-session.
// The IAM role's max duration setting can be modified between 1h to 12h.
func ExtractAWSCredentials(ctx context.Context, secret *v1.Secret, assumeRoleDuration time.Duration) (*credentials.Value, error) {
if err := ValidateAWSCredentials(secret); err != nil {
return nil, err
}
config := map[string]string{
aws.AccessKeyID: string(secret.Data[AWSAccessKeyID]),
aws.SecretAccessKey: string(secret.Data[AWSSecretAccessKey]),
aws.ConfigRole: string(secret.Data[ConfigRole]),
aws.AccessKeyID: string(secret.Data[AWSAccessKeyID]),
aws.SecretAccessKey: string(secret.Data[AWSSecretAccessKey]),
aws.ConfigRole: string(secret.Data[ConfigRole]),
aws.AssumeRoleDuration: assumeRoleDuration.String(),
}
creds, err := aws.GetCredentials(ctx, config)
if err != nil {
Expand All @@ -81,5 +94,9 @@ func ExtractAWSCredentials(ctx context.Context, secret *v1.Secret) (*credentials
if err != nil {
return nil, errors.Wrap(err, "Failed to get AWS credentials")
}
exp, err := creds.ExpiresAt()
if err == nil {
log.Debug().Print("Credential expiration", field.M{"expirationTime": exp})
}
return &val, nil
}
9 changes: 5 additions & 4 deletions pkg/secrets/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
. "gopkg.in/check.v1"
v1 "k8s.io/api/core/v1"

"github.com/kanisterio/kanister/pkg/aws"
"github.com/kanisterio/kanister/pkg/config"
)

Expand Down Expand Up @@ -75,9 +76,9 @@ func (s *AWSSecretSuite) TestExtractAWSCredentials(c *C) {
errChecker: NotNil,
},
}
for _, tc := range tcs {
creds, err := ExtractAWSCredentials(context.Background(), tc.secret)
c.Check(creds, DeepEquals, tc.expected)
for testNum, tc := range tcs {
creds, err := ExtractAWSCredentials(context.Background(), tc.secret, aws.AssumeRoleDurationDefault)
c.Check(creds, DeepEquals, tc.expected, Commentf("test number: %d", testNum))
c.Check(err, tc.errChecker)
}
}
Expand Down Expand Up @@ -110,7 +111,7 @@ func (s *AWSSecretSuite) TestExtractAWSCredentialsWithSessionToken(c *C) {
output: NotNil,
},
} {
_, err := ExtractAWSCredentials(context.Background(), tc.secret)
_, err := ExtractAWSCredentials(context.Background(), tc.secret, aws.AssumeRoleDurationDefault)
c.Check(err, tc.output)
}
}
3 changes: 2 additions & 1 deletion pkg/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"k8s.io/client-go/kubernetes"

crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
"github.com/kanisterio/kanister/pkg/aws"
"github.com/kanisterio/kanister/pkg/objectstore"
"github.com/kanisterio/kanister/pkg/param"
"github.com/kanisterio/kanister/pkg/secrets"
Expand Down Expand Up @@ -312,7 +313,7 @@ func osSecretFromProfile(ctx context.Context, pType objectstore.ProviderType, p
if err != nil {
return nil, errorf(err, "Could not fetch the secret specified in credential")
}
creds, err := secrets.ExtractAWSCredentials(ctx, s)
creds, err := secrets.ExtractAWSCredentials(ctx, s, aws.AssumeRoleDurationDefault)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 68e326e

Please sign in to comment.