s3 cache client-side support

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2022-08-30 17:00:10 +02:00
parent 6804bcbf12
commit 57d22a7bd1
305 changed files with 45257 additions and 6 deletions

View File

@ -0,0 +1,320 @@
// Package stscreds are credential Providers to retrieve STS AWS credentials.
//
// STS provides multiple ways to retrieve credentials which can be used when making
// future AWS service API operation calls.
//
// The SDK will ensure that per instance of credentials.Credentials all requests
// to refresh the credentials will be synchronized. But, the SDK is unable to
// ensure synchronous usage of the AssumeRoleProvider if the value is shared
// between multiple Credentials or service clients.
//
// Assume Role
//
// To assume an IAM role using STS with the SDK you can create a new Credentials
// with the SDKs's stscreds package.
//
// // Initial credentials loaded from SDK's default credential chain. Such as
// // the environment, shared credentials (~/.aws/credentials), or EC2 Instance
// // Role. These credentials will be used to to make the STS Assume Role API.
// cfg, err := config.LoadDefaultConfig(context.TODO())
// if err != nil {
// panic(err)
// }
//
// // Create the credentials from AssumeRoleProvider to assume the role
// // referenced by the "myRoleARN" ARN.
// stsSvc := sts.NewFromConfig(cfg)
// creds := stscreds.NewAssumeRoleProvider(stsSvc, "myRoleArn")
//
// cfg.Credentials = aws.NewCredentialsCache(creds)
//
// // Create service client value configured for credentials
// // from assumed role.
// svc := s3.NewFromConfig(cfg)
//
// Assume Role with custom MFA Token provider
//
// To assume an IAM role with a MFA token you can either specify a custom MFA
// token provider or use the SDK's built in StdinTokenProvider that will prompt
// the user for a token code each time the credentials need to to be refreshed.
// Specifying a custom token provider allows you to control where the token
// code is retrieved from, and how it is refreshed.
//
// With a custom token provider, the provider is responsible for refreshing the
// token code when called.
//
// cfg, err := config.LoadDefaultConfig(context.TODO())
// if err != nil {
// panic(err)
// }
//
// staticTokenProvider := func() (string, error) {
// return someTokenCode, nil
// }
//
// // Create the credentials from AssumeRoleProvider to assume the role
// // referenced by the "myRoleARN" ARN using the MFA token code provided.
// creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), "myRoleArn", func(o *stscreds.AssumeRoleOptions) {
// o.SerialNumber = aws.String("myTokenSerialNumber")
// o.TokenProvider = staticTokenProvider
// })
//
// cfg.Credentials = aws.NewCredentialsCache(creds)
//
// // Create service client value configured for credentials
// // from assumed role.
// svc := s3.NewFromConfig(cfg)
//
// Assume Role with MFA Token Provider
//
// To assume an IAM role with MFA for longer running tasks where the credentials
// may need to be refreshed setting the TokenProvider field of AssumeRoleProvider
// will allow the credential provider to prompt for new MFA token code when the
// role's credentials need to be refreshed.
//
// The StdinTokenProvider function is available to prompt on stdin to retrieve
// the MFA token code from the user. You can also implement custom prompts by
// satisfying the TokenProvider function signature.
//
// Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will
// have undesirable results as the StdinTokenProvider will not be synchronized. A
// single Credentials with an AssumeRoleProvider can be shared safely.
//
// cfg, err := config.LoadDefaultConfig(context.TODO())
// if err != nil {
// panic(err)
// }
//
// // Create the credentials from AssumeRoleProvider to assume the role
// // referenced by the "myRoleARN" ARN using the MFA token code provided.
// creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), "myRoleArn", func(o *stscreds.AssumeRoleOptions) {
// o.SerialNumber = aws.String("myTokenSerialNumber")
// o.TokenProvider = stscreds.StdinTokenProvider
// })
//
// cfg.Credentials = aws.NewCredentialsCache(creds)
//
// // Create service client value configured for credentials
// // from assumed role.
// svc := s3.NewFromConfig(cfg)
package stscreds
import (
"context"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/aws-sdk-go-v2/service/sts/types"
)
// StdinTokenProvider will prompt on stdout and read from stdin for a string value.
// An error is returned if reading from stdin fails.
//
// Use this function go read MFA tokens from stdin. The function makes no attempt
// to make atomic prompts from stdin across multiple gorouties.
//
// Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will
// have undesirable results as the StdinTokenProvider will not be synchronized. A
// single Credentials with an AssumeRoleProvider can be shared safely
//
// Will wait forever until something is provided on the stdin.
func StdinTokenProvider() (string, error) {
var v string
fmt.Printf("Assume Role MFA token code: ")
_, err := fmt.Scanln(&v)
return v, err
}
// ProviderName provides a name of AssumeRole provider
const ProviderName = "AssumeRoleProvider"
// AssumeRoleAPIClient is a client capable of the STS AssumeRole operation.
type AssumeRoleAPIClient interface {
AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error)
}
// DefaultDuration is the default amount of time in minutes that the
// credentials will be valid for. This value is only used by AssumeRoleProvider
// for specifying the default expiry duration of an assume role.
//
// Other providers such as WebIdentityRoleProvider do not use this value, and
// instead rely on STS API's default parameter handing to assign a default
// value.
var DefaultDuration = time.Duration(15) * time.Minute
// AssumeRoleProvider retrieves temporary credentials from the STS service, and
// keeps track of their expiration time.
//
// This credential provider will be used by the SDKs default credential change
// when shared configuration is enabled, and the shared config or shared credentials
// file configure assume role. See Session docs for how to do this.
//
// AssumeRoleProvider does not provide any synchronization and it is not safe
// to share this value across multiple Credentials, Sessions, or service clients
// without also sharing the same Credentials instance.
type AssumeRoleProvider struct {
options AssumeRoleOptions
}
// AssumeRoleOptions is the configurable options for AssumeRoleProvider
type AssumeRoleOptions struct {
// Client implementation of the AssumeRole operation. Required
Client AssumeRoleAPIClient
// IAM Role ARN to be assumed. Required
RoleARN string
// Session name, if you wish to uniquely identify this session.
RoleSessionName string
// Expiry duration of the STS credentials. Defaults to 15 minutes if not set.
Duration time.Duration
// Optional ExternalID to pass along, defaults to nil if not set.
ExternalID *string
// The policy plain text must be 2048 bytes or shorter. However, an internal
// conversion compresses it into a packed binary format with a separate limit.
// The PackedPolicySize response element indicates by percentage how close to
// the upper size limit the policy is, with 100% equaling the maximum allowed
// size.
Policy *string
// The ARNs of IAM managed policies you want to use as managed session policies.
// The policies must exist in the same account as the role.
//
// This parameter is optional. You can provide up to 10 managed policy ARNs.
// However, the plain text that you use for both inline and managed session
// policies can't exceed 2,048 characters.
//
// An AWS conversion compresses the passed session policies and session tags
// into a packed binary format that has a separate limit. Your request can fail
// for this limit even if your plain text meets the other requirements. The
// PackedPolicySize response element indicates by percentage how close the policies
// and tags for your request are to the upper size limit.
//
// Passing policies to this operation returns new temporary credentials. The
// resulting session's permissions are the intersection of the role's identity-based
// policy and the session policies. You can use the role's temporary credentials
// in subsequent AWS API calls to access resources in the account that owns
// the role. You cannot use session policies to grant more permissions than
// those allowed by the identity-based policy of the role that is being assumed.
// For more information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// in the IAM User Guide.
PolicyARNs []types.PolicyDescriptorType
// The identification number of the MFA device that is associated with the user
// who is making the AssumeRole call. Specify this value if the trust policy
// of the role being assumed includes a condition that requires MFA authentication.
// The value is either the serial number for a hardware device (such as GAHT12345678)
// or an Amazon Resource Name (ARN) for a virtual device (such as arn:aws:iam::123456789012:mfa/user).
SerialNumber *string
// The source identity specified by the principal that is calling the AssumeRole
// operation. You can require users to specify a source identity when they assume a
// role. You do this by using the sts:SourceIdentity condition key in a role trust
// policy. You can use source identity information in CloudTrail logs to determine
// who took actions with a role. You can use the aws:SourceIdentity condition key
// to further control access to Amazon Web Services resources based on the value of
// source identity. For more information about using source identity, see Monitor
// and control actions taken with assumed roles
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_control-access_monitor.html)
// in the IAM User Guide.
SourceIdentity *string
// Async method of providing MFA token code for assuming an IAM role with MFA.
// The value returned by the function will be used as the TokenCode in the Retrieve
// call. See StdinTokenProvider for a provider that prompts and reads from stdin.
//
// This token provider will be called when ever the assumed role's
// credentials need to be refreshed when SerialNumber is set.
TokenProvider func() (string, error)
// A list of session tags that you want to pass. Each session tag consists of a key
// name and an associated value. For more information about session tags, see
// Tagging STS Sessions
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html) in the
// IAM User Guide. This parameter is optional. You can pass up to 50 session tags.
Tags []types.Tag
// A list of keys for session tags that you want to set as transitive. If you set a
// tag key as transitive, the corresponding key and value passes to subsequent
// sessions in a role chain. For more information, see Chaining Roles with Session
// Tags
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining)
// in the IAM User Guide. This parameter is optional.
TransitiveTagKeys []string
}
// NewAssumeRoleProvider constructs and returns a credentials provider that
// will retrieve credentials by assuming a IAM role using STS.
func NewAssumeRoleProvider(client AssumeRoleAPIClient, roleARN string, optFns ...func(*AssumeRoleOptions)) *AssumeRoleProvider {
o := AssumeRoleOptions{
Client: client,
RoleARN: roleARN,
}
for _, fn := range optFns {
fn(&o)
}
return &AssumeRoleProvider{
options: o,
}
}
// Retrieve generates a new set of temporary credentials using STS.
func (p *AssumeRoleProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
// Apply defaults where parameters are not set.
if len(p.options.RoleSessionName) == 0 {
// Try to work out a role name that will hopefully end up unique.
p.options.RoleSessionName = fmt.Sprintf("aws-go-sdk-%d", time.Now().UTC().UnixNano())
}
if p.options.Duration == 0 {
// Expire as often as AWS permits.
p.options.Duration = DefaultDuration
}
input := &sts.AssumeRoleInput{
DurationSeconds: aws.Int32(int32(p.options.Duration / time.Second)),
PolicyArns: p.options.PolicyARNs,
RoleArn: aws.String(p.options.RoleARN),
RoleSessionName: aws.String(p.options.RoleSessionName),
ExternalId: p.options.ExternalID,
SourceIdentity: p.options.SourceIdentity,
Tags: p.options.Tags,
TransitiveTagKeys: p.options.TransitiveTagKeys,
}
if p.options.Policy != nil {
input.Policy = p.options.Policy
}
if p.options.SerialNumber != nil {
if p.options.TokenProvider != nil {
input.SerialNumber = p.options.SerialNumber
code, err := p.options.TokenProvider()
if err != nil {
return aws.Credentials{}, err
}
input.TokenCode = aws.String(code)
} else {
return aws.Credentials{}, fmt.Errorf("assume role with MFA enabled, but TokenProvider is not set")
}
}
resp, err := p.options.Client.AssumeRole(ctx, input)
if err != nil {
return aws.Credentials{Source: ProviderName}, err
}
return aws.Credentials{
AccessKeyID: *resp.Credentials.AccessKeyId,
SecretAccessKey: *resp.Credentials.SecretAccessKey,
SessionToken: *resp.Credentials.SessionToken,
Source: ProviderName,
CanExpire: true,
Expires: *resp.Credentials.Expiration,
}, nil
}

View File

@ -0,0 +1,150 @@
package stscreds
import (
"context"
"fmt"
"io/ioutil"
"strconv"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/retry"
"github.com/aws/aws-sdk-go-v2/internal/sdk"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/aws-sdk-go-v2/service/sts/types"
)
var invalidIdentityTokenExceptionCode = (&types.InvalidIdentityTokenException{}).ErrorCode()
const (
// WebIdentityProviderName is the web identity provider name
WebIdentityProviderName = "WebIdentityCredentials"
)
// AssumeRoleWithWebIdentityAPIClient is a client capable of the STS AssumeRoleWithWebIdentity operation.
type AssumeRoleWithWebIdentityAPIClient interface {
AssumeRoleWithWebIdentity(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error)
}
// WebIdentityRoleProvider is used to retrieve credentials using
// an OIDC token.
type WebIdentityRoleProvider struct {
options WebIdentityRoleOptions
}
// WebIdentityRoleOptions is a structure of configurable options for WebIdentityRoleProvider
type WebIdentityRoleOptions struct {
// Client implementation of the AssumeRoleWithWebIdentity operation. Required
Client AssumeRoleWithWebIdentityAPIClient
// JWT Token Provider. Required
TokenRetriever IdentityTokenRetriever
// IAM Role ARN to assume. Required
RoleARN string
// Session name, if you wish to uniquely identify this session.
RoleSessionName string
// Expiry duration of the STS credentials. STS will assign a default expiry
// duration if this value is unset. This is different from the Duration
// option of AssumeRoleProvider, which automatically assigns 15 minutes if
// Duration is unset.
//
// See the STS AssumeRoleWithWebIdentity API reference guide for more
// information on defaults.
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
Duration time.Duration
// An IAM policy in JSON format that you want to use as an inline session policy.
Policy *string
// The Amazon Resource Names (ARNs) of the IAM managed policies that you
// want to use as managed session policies. The policies must exist in the
// same account as the role.
PolicyARNs []types.PolicyDescriptorType
}
// IdentityTokenRetriever is an interface for retrieving a JWT
type IdentityTokenRetriever interface {
GetIdentityToken() ([]byte, error)
}
// IdentityTokenFile is for retrieving an identity token from the given file name
type IdentityTokenFile string
// GetIdentityToken retrieves the JWT token from the file and returns the contents as a []byte
func (j IdentityTokenFile) GetIdentityToken() ([]byte, error) {
b, err := ioutil.ReadFile(string(j))
if err != nil {
return nil, fmt.Errorf("unable to read file at %s: %v", string(j), err)
}
return b, nil
}
// NewWebIdentityRoleProvider will return a new WebIdentityRoleProvider with the
// provided stsiface.ClientAPI
func NewWebIdentityRoleProvider(client AssumeRoleWithWebIdentityAPIClient, roleARN string, tokenRetriever IdentityTokenRetriever, optFns ...func(*WebIdentityRoleOptions)) *WebIdentityRoleProvider {
o := WebIdentityRoleOptions{
Client: client,
RoleARN: roleARN,
TokenRetriever: tokenRetriever,
}
for _, fn := range optFns {
fn(&o)
}
return &WebIdentityRoleProvider{options: o}
}
// Retrieve will attempt to assume a role from a token which is located at
// 'WebIdentityTokenFilePath' specified destination and if that is empty an
// error will be returned.
func (p *WebIdentityRoleProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
b, err := p.options.TokenRetriever.GetIdentityToken()
if err != nil {
return aws.Credentials{}, fmt.Errorf("failed to retrieve jwt from provide source, %w", err)
}
sessionName := p.options.RoleSessionName
if len(sessionName) == 0 {
// session name is used to uniquely identify a session. This simply
// uses unix time in nanoseconds to uniquely identify sessions.
sessionName = strconv.FormatInt(sdk.NowTime().UnixNano(), 10)
}
input := &sts.AssumeRoleWithWebIdentityInput{
PolicyArns: p.options.PolicyARNs,
RoleArn: &p.options.RoleARN,
RoleSessionName: &sessionName,
WebIdentityToken: aws.String(string(b)),
}
if p.options.Duration != 0 {
// If set use the value, otherwise STS will assign a default expiration duration.
input.DurationSeconds = aws.Int32(int32(p.options.Duration / time.Second))
}
if p.options.Policy != nil {
input.Policy = p.options.Policy
}
resp, err := p.options.Client.AssumeRoleWithWebIdentity(ctx, input, func(options *sts.Options) {
options.Retryer = retry.AddWithErrorCodes(options.Retryer, invalidIdentityTokenExceptionCode)
})
if err != nil {
return aws.Credentials{}, fmt.Errorf("failed to retrieve credentials, %w", err)
}
// InvalidIdentityToken error is a temporary error that can occur
// when assuming an Role with a JWT web identity token.
value := aws.Credentials{
AccessKeyID: aws.ToString(resp.Credentials.AccessKeyId),
SecretAccessKey: aws.ToString(resp.Credentials.SecretAccessKey),
SessionToken: aws.ToString(resp.Credentials.SessionToken),
Source: WebIdentityProviderName,
CanExpire: true,
Expires: *resp.Credentials.Expiration,
}
return value, nil
}