code.dwrz.net

Go monorepo.
Log | Files | Refs

web_identity_provider.go (5324B)


      1 package stscreds
      2 
      3 import (
      4 	"context"
      5 	"fmt"
      6 	"io/ioutil"
      7 	"strconv"
      8 	"time"
      9 
     10 	"github.com/aws/aws-sdk-go-v2/aws"
     11 	"github.com/aws/aws-sdk-go-v2/aws/retry"
     12 	"github.com/aws/aws-sdk-go-v2/internal/sdk"
     13 	"github.com/aws/aws-sdk-go-v2/service/sts"
     14 	"github.com/aws/aws-sdk-go-v2/service/sts/types"
     15 )
     16 
     17 var invalidIdentityTokenExceptionCode = (&types.InvalidIdentityTokenException{}).ErrorCode()
     18 
     19 const (
     20 	// WebIdentityProviderName is the web identity provider name
     21 	WebIdentityProviderName = "WebIdentityCredentials"
     22 )
     23 
     24 // AssumeRoleWithWebIdentityAPIClient is a client capable of the STS AssumeRoleWithWebIdentity operation.
     25 type AssumeRoleWithWebIdentityAPIClient interface {
     26 	AssumeRoleWithWebIdentity(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error)
     27 }
     28 
     29 // WebIdentityRoleProvider is used to retrieve credentials using
     30 // an OIDC token.
     31 type WebIdentityRoleProvider struct {
     32 	options WebIdentityRoleOptions
     33 }
     34 
     35 // WebIdentityRoleOptions is a structure of configurable options for WebIdentityRoleProvider
     36 type WebIdentityRoleOptions struct {
     37 	// Client implementation of the AssumeRoleWithWebIdentity operation. Required
     38 	Client AssumeRoleWithWebIdentityAPIClient
     39 
     40 	// JWT Token Provider. Required
     41 	TokenRetriever IdentityTokenRetriever
     42 
     43 	// IAM Role ARN to assume. Required
     44 	RoleARN string
     45 
     46 	// Session name, if you wish to uniquely identify this session.
     47 	RoleSessionName string
     48 
     49 	// Expiry duration of the STS credentials. STS will assign a default expiry
     50 	// duration if this value is unset. This is different from the Duration
     51 	// option of AssumeRoleProvider, which automatically assigns 15 minutes if
     52 	// Duration is unset.
     53 	//
     54 	// See the STS AssumeRoleWithWebIdentity API reference guide for more
     55 	// information on defaults.
     56 	// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
     57 	Duration time.Duration
     58 
     59 	// An IAM policy in JSON format that you want to use as an inline session policy.
     60 	Policy *string
     61 
     62 	// The Amazon Resource Names (ARNs) of the IAM managed policies that you
     63 	// want to use as managed session policies.  The policies must exist in the
     64 	// same account as the role.
     65 	PolicyARNs []types.PolicyDescriptorType
     66 }
     67 
     68 // IdentityTokenRetriever is an interface for retrieving a JWT
     69 type IdentityTokenRetriever interface {
     70 	GetIdentityToken() ([]byte, error)
     71 }
     72 
     73 // IdentityTokenFile is for retrieving an identity token from the given file name
     74 type IdentityTokenFile string
     75 
     76 // GetIdentityToken retrieves the JWT token from the file and returns the contents as a []byte
     77 func (j IdentityTokenFile) GetIdentityToken() ([]byte, error) {
     78 	b, err := ioutil.ReadFile(string(j))
     79 	if err != nil {
     80 		return nil, fmt.Errorf("unable to read file at %s: %v", string(j), err)
     81 	}
     82 
     83 	return b, nil
     84 }
     85 
     86 // NewWebIdentityRoleProvider will return a new WebIdentityRoleProvider with the
     87 // provided stsiface.ClientAPI
     88 func NewWebIdentityRoleProvider(client AssumeRoleWithWebIdentityAPIClient, roleARN string, tokenRetriever IdentityTokenRetriever, optFns ...func(*WebIdentityRoleOptions)) *WebIdentityRoleProvider {
     89 	o := WebIdentityRoleOptions{
     90 		Client:         client,
     91 		RoleARN:        roleARN,
     92 		TokenRetriever: tokenRetriever,
     93 	}
     94 
     95 	for _, fn := range optFns {
     96 		fn(&o)
     97 	}
     98 
     99 	return &WebIdentityRoleProvider{options: o}
    100 }
    101 
    102 // Retrieve will attempt to assume a role from a token which is located at
    103 // 'WebIdentityTokenFilePath' specified destination and if that is empty an
    104 // error will be returned.
    105 func (p *WebIdentityRoleProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
    106 	b, err := p.options.TokenRetriever.GetIdentityToken()
    107 	if err != nil {
    108 		return aws.Credentials{}, fmt.Errorf("failed to retrieve jwt from provide source, %w", err)
    109 	}
    110 
    111 	sessionName := p.options.RoleSessionName
    112 	if len(sessionName) == 0 {
    113 		// session name is used to uniquely identify a session. This simply
    114 		// uses unix time in nanoseconds to uniquely identify sessions.
    115 		sessionName = strconv.FormatInt(sdk.NowTime().UnixNano(), 10)
    116 	}
    117 	input := &sts.AssumeRoleWithWebIdentityInput{
    118 		PolicyArns:       p.options.PolicyARNs,
    119 		RoleArn:          &p.options.RoleARN,
    120 		RoleSessionName:  &sessionName,
    121 		WebIdentityToken: aws.String(string(b)),
    122 	}
    123 	if p.options.Duration != 0 {
    124 		// If set use the value, otherwise STS will assign a default expiration duration.
    125 		input.DurationSeconds = aws.Int32(int32(p.options.Duration / time.Second))
    126 	}
    127 	if p.options.Policy != nil {
    128 		input.Policy = p.options.Policy
    129 	}
    130 
    131 	resp, err := p.options.Client.AssumeRoleWithWebIdentity(ctx, input, func(options *sts.Options) {
    132 		options.Retryer = retry.AddWithErrorCodes(options.Retryer, invalidIdentityTokenExceptionCode)
    133 	})
    134 	if err != nil {
    135 		return aws.Credentials{}, fmt.Errorf("failed to retrieve credentials, %w", err)
    136 	}
    137 
    138 	// InvalidIdentityToken error is a temporary error that can occur
    139 	// when assuming an Role with a JWT web identity token.
    140 
    141 	value := aws.Credentials{
    142 		AccessKeyID:     aws.ToString(resp.Credentials.AccessKeyId),
    143 		SecretAccessKey: aws.ToString(resp.Credentials.SecretAccessKey),
    144 		SessionToken:    aws.ToString(resp.Credentials.SessionToken),
    145 		Source:          WebIdentityProviderName,
    146 		CanExpire:       true,
    147 		Expires:         *resp.Credentials.Expiration,
    148 	}
    149 	return value, nil
    150 }