|
Packit |
63bb0d |
/*
|
|
Packit |
63bb0d |
Package stscreds are credential Providers to retrieve STS AWS credentials.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
STS provides multiple ways to retrieve credentials which can be used when making
|
|
Packit |
63bb0d |
future AWS service API operation calls.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
The SDK will ensure that per instance of credentials.Credentials all requests
|
|
Packit |
63bb0d |
to refresh the credentials will be synchronized. But, the SDK is unable to
|
|
Packit |
63bb0d |
ensure synchronous usage of the AssumeRoleProvider if the value is shared
|
|
Packit |
63bb0d |
between multiple Credentials, Sessions or service clients.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
Assume Role
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
To assume an IAM role using STS with the SDK you can create a new Credentials
|
|
Packit |
63bb0d |
with the SDKs's stscreds package.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Initial credentials loaded from SDK's default credential chain. Such as
|
|
Packit |
63bb0d |
// the environment, shared credentials (~/.aws/credentials), or EC2 Instance
|
|
Packit |
63bb0d |
// Role. These credentials will be used to to make the STS Assume Role API.
|
|
Packit |
63bb0d |
sess := session.Must(session.NewSession())
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Create the credentials from AssumeRoleProvider to assume the role
|
|
Packit |
63bb0d |
// referenced by the "myRoleARN" ARN.
|
|
Packit |
63bb0d |
creds := stscreds.NewCredentials(sess, "myRoleArn")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Create service client value configured for credentials
|
|
Packit |
63bb0d |
// from assumed role.
|
|
Packit |
63bb0d |
svc := s3.New(sess, &aws.Config{Credentials: creds})
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
Assume Role with static MFA Token
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
To assume an IAM role with a MFA token you can either specify a MFA token code
|
|
Packit |
63bb0d |
directly or provide a function to prompt the user each time the credentials
|
|
Packit |
63bb0d |
need to refresh the role's credentials. Specifying the TokenCode should be used
|
|
Packit |
63bb0d |
for short lived operations that will not need to be refreshed, and when you do
|
|
Packit |
63bb0d |
not want to have direct control over the user provides their MFA token.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
With TokenCode the AssumeRoleProvider will be not be able to refresh the role's
|
|
Packit |
63bb0d |
credentials.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Create the credentials from AssumeRoleProvider to assume the role
|
|
Packit |
63bb0d |
// referenced by the "myRoleARN" ARN using the MFA token code provided.
|
|
Packit |
63bb0d |
creds := stscreds.NewCredentials(sess, "myRoleArn", func(p *stscreds.AssumeRoleProvider) {
|
|
Packit |
63bb0d |
p.SerialNumber = aws.String("myTokenSerialNumber")
|
|
Packit |
63bb0d |
p.TokenCode = aws.String("00000000")
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Create service client value configured for credentials
|
|
Packit |
63bb0d |
// from assumed role.
|
|
Packit |
63bb0d |
svc := s3.New(sess, &aws.Config{Credentials: creds})
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
Assume Role with MFA Token Provider
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
To assume an IAM role with MFA for longer running tasks where the credentials
|
|
Packit |
63bb0d |
may need to be refreshed setting the TokenProvider field of AssumeRoleProvider
|
|
Packit |
63bb0d |
will allow the credential provider to prompt for new MFA token code when the
|
|
Packit |
63bb0d |
role's credentials need to be refreshed.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
The StdinTokenProvider function is available to prompt on stdin to retrieve
|
|
Packit |
63bb0d |
the MFA token code from the user. You can also implement custom prompts by
|
|
Packit |
63bb0d |
satisfing the TokenProvider function signature.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will
|
|
Packit |
63bb0d |
have undesirable results as the StdinTokenProvider will not be synchronized. A
|
|
Packit |
63bb0d |
single Credentials with an AssumeRoleProvider can be shared safely.
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Create the credentials from AssumeRoleProvider to assume the role
|
|
Packit |
63bb0d |
// referenced by the "myRoleARN" ARN. Prompting for MFA token from stdin.
|
|
Packit |
63bb0d |
creds := stscreds.NewCredentials(sess, "myRoleArn", func(p *stscreds.AssumeRoleProvider) {
|
|
Packit |
63bb0d |
p.SerialNumber = aws.String("myTokenSerialNumber")
|
|
Packit |
63bb0d |
p.TokenProvider = stscreds.StdinTokenProvider
|
|
Packit |
63bb0d |
})
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Create service client value configured for credentials
|
|
Packit |
63bb0d |
// from assumed role.
|
|
Packit |
63bb0d |
svc := s3.New(sess, &aws.Config{Credentials: creds})
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
*/
|
|
Packit |
63bb0d |
package stscreds
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
import (
|
|
Packit |
63bb0d |
"fmt"
|
|
Packit |
63bb0d |
"os"
|
|
Packit |
63bb0d |
"time"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/aws"
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/aws/awserr"
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/aws/client"
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/internal/sdkrand"
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/service/sts"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// StdinTokenProvider will prompt on stderr and read from stdin for a string value.
|
|
Packit |
63bb0d |
// An error is returned if reading from stdin fails.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// Use this function go read MFA tokens from stdin. The function makes no attempt
|
|
Packit |
63bb0d |
// to make atomic prompts from stdin across multiple gorouties.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will
|
|
Packit |
63bb0d |
// have undesirable results as the StdinTokenProvider will not be synchronized. A
|
|
Packit |
63bb0d |
// single Credentials with an AssumeRoleProvider can be shared safely
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// Will wait forever until something is provided on the stdin.
|
|
Packit |
63bb0d |
func StdinTokenProvider() (string, error) {
|
|
Packit |
63bb0d |
var v string
|
|
Packit |
63bb0d |
fmt.Fprintf(os.Stderr, "Assume Role MFA token code: ")
|
|
Packit |
63bb0d |
_, err := fmt.Scanln(&v)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return v, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ProviderName provides a name of AssumeRole provider
|
|
Packit |
63bb0d |
const ProviderName = "AssumeRoleProvider"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// AssumeRoler represents the minimal subset of the STS client API used by this provider.
|
|
Packit |
63bb0d |
type AssumeRoler interface {
|
|
Packit |
63bb0d |
AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// DefaultDuration is the default amount of time in minutes that the credentials
|
|
Packit |
63bb0d |
// will be valid for.
|
|
Packit |
63bb0d |
var DefaultDuration = time.Duration(15) * time.Minute
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// AssumeRoleProvider retrieves temporary credentials from the STS service, and
|
|
Packit |
63bb0d |
// keeps track of their expiration time.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// This credential provider will be used by the SDKs default credential change
|
|
Packit |
63bb0d |
// when shared configuration is enabled, and the shared config or shared credentials
|
|
Packit |
63bb0d |
// file configure assume role. See Session docs for how to do this.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// AssumeRoleProvider does not provide any synchronization and it is not safe
|
|
Packit |
63bb0d |
// to share this value across multiple Credentials, Sessions, or service clients
|
|
Packit |
63bb0d |
// without also sharing the same Credentials instance.
|
|
Packit |
63bb0d |
type AssumeRoleProvider struct {
|
|
Packit |
63bb0d |
credentials.Expiry
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// STS client to make assume role request with.
|
|
Packit |
63bb0d |
Client AssumeRoler
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Role to be assumed.
|
|
Packit |
63bb0d |
RoleARN string
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Session name, if you wish to reuse the credentials elsewhere.
|
|
Packit |
63bb0d |
RoleSessionName string
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Expiry duration of the STS credentials. Defaults to 15 minutes if not set.
|
|
Packit |
63bb0d |
Duration time.Duration
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Optional ExternalID to pass along, defaults to nil if not set.
|
|
Packit |
63bb0d |
ExternalID *string
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// The policy plain text must be 2048 bytes or shorter. However, an internal
|
|
Packit |
63bb0d |
// conversion compresses it into a packed binary format with a separate limit.
|
|
Packit |
63bb0d |
// The PackedPolicySize response element indicates by percentage how close to
|
|
Packit |
63bb0d |
// the upper size limit the policy is, with 100% equaling the maximum allowed
|
|
Packit |
63bb0d |
// size.
|
|
Packit |
63bb0d |
Policy *string
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// The identification number of the MFA device that is associated with the user
|
|
Packit |
63bb0d |
// who is making the AssumeRole call. Specify this value if the trust policy
|
|
Packit |
63bb0d |
// of the role being assumed includes a condition that requires MFA authentication.
|
|
Packit |
63bb0d |
// The value is either the serial number for a hardware device (such as GAHT12345678)
|
|
Packit |
63bb0d |
// or an Amazon Resource Name (ARN) for a virtual device (such as arn:aws:iam::123456789012:mfa/user).
|
|
Packit |
63bb0d |
SerialNumber *string
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// The value provided by the MFA device, if the trust policy of the role being
|
|
Packit |
63bb0d |
// assumed requires MFA (that is, if the policy includes a condition that tests
|
|
Packit |
63bb0d |
// for MFA). If the role being assumed requires MFA and if the TokenCode value
|
|
Packit |
63bb0d |
// is missing or expired, the AssumeRole call returns an "access denied" error.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// If SerialNumber is set and neither TokenCode nor TokenProvider are also
|
|
Packit |
63bb0d |
// set an error will be returned.
|
|
Packit |
63bb0d |
TokenCode *string
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Async method of providing MFA token code for assuming an IAM role with MFA.
|
|
Packit |
63bb0d |
// The value returned by the function will be used as the TokenCode in the Retrieve
|
|
Packit |
63bb0d |
// call. See StdinTokenProvider for a provider that prompts and reads from stdin.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// This token provider will be called when ever the assumed role's
|
|
Packit |
63bb0d |
// credentials need to be refreshed when SerialNumber is also set and
|
|
Packit |
63bb0d |
// TokenCode is not set.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// If both TokenCode and TokenProvider is set, TokenProvider will be used and
|
|
Packit |
63bb0d |
// TokenCode is ignored.
|
|
Packit |
63bb0d |
TokenProvider func() (string, error)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// ExpiryWindow will allow the credentials to trigger refreshing prior to
|
|
Packit |
63bb0d |
// the credentials actually expiring. This is beneficial so race conditions
|
|
Packit |
63bb0d |
// with expiring credentials do not cause request to fail unexpectedly
|
|
Packit |
63bb0d |
// due to ExpiredTokenException exceptions.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// So a ExpiryWindow of 10s would cause calls to IsExpired() to return true
|
|
Packit |
63bb0d |
// 10 seconds before the credentials are actually expired.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// If ExpiryWindow is 0 or less it will be ignored.
|
|
Packit |
63bb0d |
ExpiryWindow time.Duration
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// MaxJitterFrac reduces the effective Duration of each credential requested
|
|
Packit |
63bb0d |
// by a random percentage between 0 and MaxJitterFraction. MaxJitterFrac must
|
|
Packit |
63bb0d |
// have a value between 0 and 1. Any other value may lead to expected behavior.
|
|
Packit |
63bb0d |
// With a MaxJitterFrac value of 0, default) will no jitter will be used.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// For example, with a Duration of 30m and a MaxJitterFrac of 0.1, the
|
|
Packit |
63bb0d |
// AssumeRole call will be made with an arbitrary Duration between 27m and
|
|
Packit |
63bb0d |
// 30m.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// MaxJitterFrac should not be negative.
|
|
Packit |
63bb0d |
MaxJitterFrac float64
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// NewCredentials returns a pointer to a new Credentials object wrapping the
|
|
Packit |
63bb0d |
// AssumeRoleProvider. The credentials will expire every 15 minutes and the
|
|
Packit |
63bb0d |
// role will be named after a nanosecond timestamp of this operation.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// Takes a Config provider to create the STS client. The ConfigProvider is
|
|
Packit |
63bb0d |
// satisfied by the session.Session type.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// It is safe to share the returned Credentials with multiple Sessions and
|
|
Packit |
63bb0d |
// service clients. All access to the credentials and refreshing them
|
|
Packit |
63bb0d |
// will be synchronized.
|
|
Packit |
63bb0d |
func NewCredentials(c client.ConfigProvider, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
|
|
Packit |
63bb0d |
p := &AssumeRoleProvider{
|
|
Packit |
63bb0d |
Client: sts.New(c),
|
|
Packit |
63bb0d |
RoleARN: roleARN,
|
|
Packit |
63bb0d |
Duration: DefaultDuration,
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
for _, option := range options {
|
|
Packit |
63bb0d |
option(p)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return credentials.NewCredentials(p)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// NewCredentialsWithClient returns a pointer to a new Credentials object wrapping the
|
|
Packit |
63bb0d |
// AssumeRoleProvider. The credentials will expire every 15 minutes and the
|
|
Packit |
63bb0d |
// role will be named after a nanosecond timestamp of this operation.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// Takes an AssumeRoler which can be satisfied by the STS client.
|
|
Packit |
63bb0d |
//
|
|
Packit |
63bb0d |
// It is safe to share the returned Credentials with multiple Sessions and
|
|
Packit |
63bb0d |
// service clients. All access to the credentials and refreshing them
|
|
Packit |
63bb0d |
// will be synchronized.
|
|
Packit |
63bb0d |
func NewCredentialsWithClient(svc AssumeRoler, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
|
|
Packit |
63bb0d |
p := &AssumeRoleProvider{
|
|
Packit |
63bb0d |
Client: svc,
|
|
Packit |
63bb0d |
RoleARN: roleARN,
|
|
Packit |
63bb0d |
Duration: DefaultDuration,
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
for _, option := range options {
|
|
Packit |
63bb0d |
option(p)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return credentials.NewCredentials(p)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Retrieve generates a new set of temporary credentials using STS.
|
|
Packit |
63bb0d |
func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) {
|
|
Packit |
63bb0d |
// Apply defaults where parameters are not set.
|
|
Packit |
63bb0d |
if p.RoleSessionName == "" {
|
|
Packit |
63bb0d |
// Try to work out a role name that will hopefully end up unique.
|
|
Packit |
63bb0d |
p.RoleSessionName = fmt.Sprintf("%d", time.Now().UTC().UnixNano())
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if p.Duration == 0 {
|
|
Packit |
63bb0d |
// Expire as often as AWS permits.
|
|
Packit |
63bb0d |
p.Duration = DefaultDuration
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
jitter := time.Duration(sdkrand.SeededRand.Float64() * p.MaxJitterFrac * float64(p.Duration))
|
|
Packit |
63bb0d |
input := &sts.AssumeRoleInput{
|
|
Packit |
63bb0d |
DurationSeconds: aws.Int64(int64((p.Duration - jitter) / time.Second)),
|
|
Packit |
63bb0d |
RoleArn: aws.String(p.RoleARN),
|
|
Packit |
63bb0d |
RoleSessionName: aws.String(p.RoleSessionName),
|
|
Packit |
63bb0d |
ExternalId: p.ExternalID,
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if p.Policy != nil {
|
|
Packit |
63bb0d |
input.Policy = p.Policy
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if p.SerialNumber != nil {
|
|
Packit |
63bb0d |
if p.TokenCode != nil {
|
|
Packit |
63bb0d |
input.SerialNumber = p.SerialNumber
|
|
Packit |
63bb0d |
input.TokenCode = p.TokenCode
|
|
Packit |
63bb0d |
} else if p.TokenProvider != nil {
|
|
Packit |
63bb0d |
input.SerialNumber = p.SerialNumber
|
|
Packit |
63bb0d |
code, err := p.TokenProvider()
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return credentials.Value{ProviderName: ProviderName}, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
input.TokenCode = aws.String(code)
|
|
Packit |
63bb0d |
} else {
|
|
Packit |
63bb0d |
return credentials.Value{ProviderName: ProviderName},
|
|
Packit |
63bb0d |
awserr.New("AssumeRoleTokenNotAvailable",
|
|
Packit |
63bb0d |
"assume role with MFA enabled, but neither TokenCode nor TokenProvider are set", nil)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
roleOutput, err := p.Client.AssumeRole(input)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return credentials.Value{ProviderName: ProviderName}, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// We will proactively generate new credentials before they expire.
|
|
Packit |
63bb0d |
p.SetExpiration(*roleOutput.Credentials.Expiration, p.ExpiryWindow)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return credentials.Value{
|
|
Packit |
63bb0d |
AccessKeyID: *roleOutput.Credentials.AccessKeyId,
|
|
Packit |
63bb0d |
SecretAccessKey: *roleOutput.Credentials.SecretAccessKey,
|
|
Packit |
63bb0d |
SessionToken: *roleOutput.Credentials.SessionToken,
|
|
Packit |
63bb0d |
ProviderName: ProviderName,
|
|
Packit |
63bb0d |
}, nil
|
|
Packit |
63bb0d |
}
|