Skip to content

Commit

Permalink
feat: support AWS assume role
Browse files Browse the repository at this point in the history
  • Loading branch information
ekristen authored and MichaelEischer committed Jan 6, 2024
1 parent b2b7669 commit 5ffb536
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 32 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/.idea
/restic
/restic.exe
/.vagrant
Expand Down
14 changes: 14 additions & 0 deletions changelog/unreleased/issue-4472
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Enhancement: Allow AWS Assume Role to be used for S3 backend

Previously only credentials discovered via the Minio Click discovery methods
would be used to authenticate. However there are many circumstances where the
discovered credentials have lower permissions and need to assume a specific role.

New Environment Variables:

- RESTIC_AWS_ASSUME_ROLE_ARN
- RESTIC_AWS_ASSUME_ROLE_SESSION_NAME
- RESTIC_AWS_ASSUME_ROLE_EXTERNAL_ID
- RESTIC_AWS_ASSUME_ROLE_REGION (if need to override from us-east-1)
- RESTIC_AWS_ASSUME_ROLE_POLICY
- RESTIC_AWS_ASSUME_ROLE_STS_ENDPOINT
4 changes: 4 additions & 0 deletions doc/040_backup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,10 @@ environment variables. The following lists these environment variables:
AWS_DEFAULT_REGION Amazon S3 default region
AWS_PROFILE Amazon credentials profile (alternative to specifying key and region)
AWS_SHARED_CREDENTIALS_FILE Location of the AWS CLI shared credentials file (default: ~/.aws/credentials)
RESTIC_AWS_ASSUME_ROLE_ARN Amazon IAM Role ARN to assume using discovered credentials
RESTIC_AWS_ASSUME_ROLE_SESSION_NAME Session Name to use with the role assumption
RESTIC_AWS_ASSUME_ROLE_EXTERNAL_ID External ID to use with the role assumption
RESTIC_AWS_ASSUME_ROLE_REGION Region to use for IAM calls for the role assumption
AZURE_ACCOUNT_NAME Account name for Azure
AZURE_ACCOUNT_KEY Account key for Azure
Expand Down
120 changes: 88 additions & 32 deletions internal/backend/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,52 @@ func open(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, erro
minio.MaxRetry = int(cfg.MaxRetries)
}

creds, err := getCredentials(cfg)
if err != nil {
return nil, errors.Wrap(err, "s3.getCredentials")
}

options := &minio.Options{
Creds: creds,
Secure: !cfg.UseHTTP,
Region: cfg.Region,
Transport: rt,
}

switch strings.ToLower(cfg.BucketLookup) {
case "", "auto":
options.BucketLookup = minio.BucketLookupAuto
case "dns":
options.BucketLookup = minio.BucketLookupDNS
case "path":
options.BucketLookup = minio.BucketLookupPath
default:
return nil, fmt.Errorf(`bad bucket-lookup style %q must be "auto", "path" or "dns"`, cfg.BucketLookup)
}

client, err := minio.New(cfg.Endpoint, options)
if err != nil {
return nil, errors.Wrap(err, "minio.New")
}

be := &Backend{
client: client,
cfg: cfg,
}

l, err := layout.ParseLayout(ctx, be, cfg.Layout, defaultLayout, cfg.Prefix)
if err != nil {
return nil, err
}

be.Layout = l

return be, nil
}

// getCredentials -- runs through the various credential types and returns the first one that works.
// additionally if the user has specified a role to assume, it will do that as well.
func getCredentials(cfg Config) (*credentials.Credentials, error) {
// Chains all credential types, in the following order:
// - Static credentials provided by user
// - AWS env vars (i.e. AWS_ACCESS_KEY_ID)
Expand All @@ -61,13 +107,13 @@ func open(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, erro
// call to a pre-defined endpoint, only valid inside
// configured ec2 instances)
creds := credentials.NewChainCredentials([]credentials.Provider{
&credentials.EnvAWS{},
&credentials.Static{
Value: credentials.Value{
AccessKeyID: cfg.KeyID,
SecretAccessKey: cfg.Secret.Unwrap(),
},
},
&credentials.EnvAWS{},
&credentials.EnvMinio{},
&credentials.FileAWSCredentials{},
&credentials.FileMinioClient{},
Expand All @@ -87,42 +133,52 @@ func open(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, erro
debug.Log("using anonymous access for %#v", cfg.Endpoint)
}

options := &minio.Options{
Creds: creds,
Secure: !cfg.UseHTTP,
Region: cfg.Region,
Transport: rt,
}

switch strings.ToLower(cfg.BucketLookup) {
case "", "auto":
options.BucketLookup = minio.BucketLookupAuto
case "dns":
options.BucketLookup = minio.BucketLookupDNS
case "path":
options.BucketLookup = minio.BucketLookupPath
default:
return nil, fmt.Errorf(`bad bucket-lookup style %q must be "auto", "path" or "dns"`, cfg.BucketLookup)
}
roleArn := os.Getenv("RESTIC_AWS_ASSUME_ROLE_ARN")
if roleArn != "" {
// use the region provided by the configuration by default
awsRegion := cfg.Region
// allow the region to be overridden if for some reason it is required
if len(os.Getenv("RESTIC_AWS_ASSUME_ROLE_REGION")) > 0 {
awsRegion = os.Getenv("RESTIC_AWS_ASSUME_ROLE_REGION")
}

client, err := minio.New(cfg.Endpoint, options)
if err != nil {
return nil, errors.Wrap(err, "minio.New")
}
sessionName := os.Getenv("RESTIC_AWS_ASSUME_ROLE_SESSION_NAME")
externalID := os.Getenv("RESTIC_AWS_ASSUME_ROLE_EXTERNAL_ID")
policy := os.Getenv("RESTIC_AWS_ASSUME_ROLE_POLICY")
stsEndpoint := os.Getenv("RESTIC_AWS_ASSUME_ROLE_STS_ENDPOINT")

if stsEndpoint == "" {
if len(awsRegion) > 0 {
if strings.HasPrefix(awsRegion, "cn-") {
stsEndpoint = "https://sts." + awsRegion + ".amazonaws.com.cn"
} else {
stsEndpoint = "https://sts." + awsRegion + ".amazonaws.com"
}
} else {
stsEndpoint = "https://sts.amazonaws.com"
}
}

be := &Backend{
client: client,
cfg: cfg,
}
opts := credentials.STSAssumeRoleOptions{
RoleARN: roleArn,
AccessKey: c.AccessKeyID,
SecretKey: c.SecretAccessKey,
SessionToken: c.SessionToken,
RoleSessionName: sessionName,
ExternalID: externalID,
Policy: policy,
}
if len(awsRegion) > 0 {
opts.Location = awsRegion
}

l, err := layout.ParseLayout(ctx, be, cfg.Layout, defaultLayout, cfg.Prefix)
if err != nil {
return nil, err
creds, err = credentials.NewSTSAssumeRole(stsEndpoint, opts)
if err != nil {
return nil, errors.Wrap(err, "creds.AssumeRole")
}
}

be.Layout = l

return be, nil
return creds, nil
}

// Open opens the S3 backend at bucket and region. The bucket is created if it
Expand Down

0 comments on commit 5ffb536

Please sign in to comment.