aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_token_provider.go
blob: 7f4fc54677221afed8a84fff482daf21c6ccb9a9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package ssocreds

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/internal/sdk"
	"github.com/aws/aws-sdk-go-v2/service/ssooidc"
	"github.com/aws/smithy-go/auth/bearer"
)

// CreateTokenAPIClient provides the interface for the SSOTokenProvider's API
// client for calling CreateToken operation to refresh the SSO token.
type CreateTokenAPIClient interface {
	CreateToken(context.Context, *ssooidc.CreateTokenInput, ...func(*ssooidc.Options)) (
		*ssooidc.CreateTokenOutput, error,
	)
}

// SSOTokenProviderOptions provides the options for configuring the
// SSOTokenProvider.
type SSOTokenProviderOptions struct {
	// Client that can be overridden
	Client CreateTokenAPIClient

	// The set of API Client options to be applied when invoking the
	// CreateToken operation.
	ClientOptions []func(*ssooidc.Options)

	// The path the file containing the cached SSO token will be read from.
	// Initialized the NewSSOTokenProvider's cachedTokenFilepath parameter.
	CachedTokenFilepath string
}

// SSOTokenProvider provides an utility for refreshing SSO AccessTokens for
// Bearer Authentication. The SSOTokenProvider can only be used to refresh
// already cached SSO Tokens. This utility cannot perform the initial SSO
// create token.
//
// The SSOTokenProvider is not safe to use concurrently. It must be wrapped in
// a utility such as smithy-go's auth/bearer#TokenCache. The SDK's
// config.LoadDefaultConfig will automatically wrap the SSOTokenProvider with
// the smithy-go TokenCache, if the external configuration loaded configured
// for an SSO session.
//
// The initial SSO create token should be preformed with the AWS CLI before the
// Go application using the SSOTokenProvider will need to retrieve the SSO
// token. If the AWS CLI has not created the token cache file, this provider
// will return an error when attempting to retrieve the cached token.
//
// This provider will attempt to refresh the cached SSO token periodically if
// needed when RetrieveBearerToken is called.
//
// A utility such as the AWS CLI must be used to initially create the SSO
// session and cached token file.
// https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html
type SSOTokenProvider struct {
	options SSOTokenProviderOptions
}

var _ bearer.TokenProvider = (*SSOTokenProvider)(nil)

// NewSSOTokenProvider returns an initialized SSOTokenProvider that will
// periodically refresh the SSO token cached stored in the cachedTokenFilepath.
// The cachedTokenFilepath file's content will be rewritten by the token
// provider when the token is refreshed.
//
// The client must be configured for the AWS region the SSO token was created for.
func NewSSOTokenProvider(client CreateTokenAPIClient, cachedTokenFilepath string, optFns ...func(o *SSOTokenProviderOptions)) *SSOTokenProvider {
	options := SSOTokenProviderOptions{
		Client:              client,
		CachedTokenFilepath: cachedTokenFilepath,
	}
	for _, fn := range optFns {
		fn(&options)
	}

	provider := &SSOTokenProvider{
		options: options,
	}

	return provider
}

// RetrieveBearerToken returns the SSO token stored in the cachedTokenFilepath
// the SSOTokenProvider was created with. If the token has expired
// RetrieveBearerToken will attempt to refresh it. If the token cannot be
// refreshed or is not present an error will be returned.
//
// A utility such as the AWS CLI must be used to initially create the SSO
// session and cached token file. https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html
func (p SSOTokenProvider) RetrieveBearerToken(ctx context.Context) (bearer.Token, error) {
	cachedToken, err := loadCachedToken(p.options.CachedTokenFilepath)
	if err != nil {
		return bearer.Token{}, err
	}

	if cachedToken.ExpiresAt != nil && sdk.NowTime().After(time.Time(*cachedToken.ExpiresAt)) {
		cachedToken, err = p.refreshToken(ctx, cachedToken)
		if err != nil {
			return bearer.Token{}, fmt.Errorf("refresh cached SSO token failed, %w", err)
		}
	}

	expiresAt := aws.ToTime((*time.Time)(cachedToken.ExpiresAt))
	return bearer.Token{
		Value:     cachedToken.AccessToken,
		CanExpire: !expiresAt.IsZero(),
		Expires:   expiresAt,
	}, nil
}

func (p SSOTokenProvider) refreshToken(ctx context.Context, cachedToken token) (token, error) {
	if cachedToken.ClientSecret == "" || cachedToken.ClientID == "" || cachedToken.RefreshToken == "" {
		return token{}, fmt.Errorf("cached SSO token is expired, or not present, and cannot be refreshed")
	}

	createResult, err := p.options.Client.CreateToken(ctx, &ssooidc.CreateTokenInput{
		ClientId:     &cachedToken.ClientID,
		ClientSecret: &cachedToken.ClientSecret,
		RefreshToken: &cachedToken.RefreshToken,
		GrantType:    aws.String("refresh_token"),
	}, p.options.ClientOptions...)
	if err != nil {
		return token{}, fmt.Errorf("unable to refresh SSO token, %w", err)
	}

	expiresAt := sdk.NowTime().Add(time.Duration(createResult.ExpiresIn) * time.Second)

	cachedToken.AccessToken = aws.ToString(createResult.AccessToken)
	cachedToken.ExpiresAt = (*rfc3339)(&expiresAt)
	cachedToken.RefreshToken = aws.ToString(createResult.RefreshToken)

	fileInfo, err := os.Stat(p.options.CachedTokenFilepath)
	if err != nil {
		return token{}, fmt.Errorf("failed to stat cached SSO token file %w", err)
	}

	if err = storeCachedToken(p.options.CachedTokenFilepath, cachedToken, fileInfo.Mode()); err != nil {
		return token{}, fmt.Errorf("unable to cache refreshed SSO token, %w", err)
	}

	return cachedToken, nil
}