Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sso token provider #4853

Merged
merged 29 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8855191
Squash and Merge sso token provider code and unit test
May 23, 2023
3bf12a6
Squash and Merge ssocred rfc3339 code location change
May 24, 2023
9e666ea
Squash and Merge pending changelog content
May 24, 2023
e3644bf
Deprecate smithy go dependency and Merge bearer token code
May 24, 2023
22968a0
Deprecate smithy go dependency and Merge bearer token code
May 24, 2023
6354a34
Deprecate go-cmp dependency
May 24, 2023
3e9df76
Merge changed sso token provider error format
May 24, 2023
df54980
Merge branch 'main' into feature-token-provider
May 24, 2023
14a703c
Merge modified pending changelog
May 24, 2023
6f16394
Deleted sms smoke json model that is generated from unknown merging f…
May 24, 2023
d0894cf
Modify and merge sso token provider logic
May 25, 2023
d5e1072
Merge branch 'feat-sso-session' into feature-token-provider
May 25, 2023
45be7cf
Modify and Merge changelog entry
May 25, 2023
7b47ebb
Merge branch 'feat-sso-session' into feature-token-provider
May 25, 2023
e35aaee
Modify and Merge changelog entry
May 25, 2023
3154ff0
Modify and Merge changelog entry
May 25, 2023
3c021ea
Modify and Merge changelog entry
May 26, 2023
359ee1e
Modify and Merge bearer token directory and sso token provider var name
May 26, 2023
19a523b
Modify and Merge changelog entry
May 26, 2023
da6f599
Modify and Merge changelog entry
May 26, 2023
8cf9168
Merge branch 'feat-sso-session' into feature-token-provider
wty-Bryant May 26, 2023
8affb62
Modify and Merge context used in token provider
May 26, 2023
2ffc7e2
Modify and Merge ssocreds test tag
May 30, 2023
6e4eb05
Modify and Merge ssocreds token provider test tag
May 30, 2023
b2ca51c
Modify and Merge ssocreds cached token test tag
May 30, 2023
9b2a2a1
Modify and Merge ssocreds cached token test tag
May 31, 2023
7564841
Modify and Merge ssocreds token provider test tag
May 31, 2023
1558f1a
Modify and Merge ssocreds token provider test tag
May 31, 2023
89c821a
Modify and Merge bearer token directory
May 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
### SDK Enhancements

### SDK Bugs
* `aws/credentials/ssocreds`: Implement SSO token provider to support for `sso-session` in AWS shared config.
* Fixes [4649](https://github.com/aws/aws-sdk-go/issues/4649)
49 changes: 49 additions & 0 deletions aws/auth/bearer/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package bearer

import (
"github.com/aws/aws-sdk-go/aws"
"time"
)

type Token struct {
Value string

CanExpire bool
Expires time.Time
}

// Expired returns if the token's Expires time is before or equal to the time
// provided. If CanExpires is false, Expired will always return false.
func (t Token) Expired(now time.Time) bool {
if !t.CanExpire {
return false
}
now = now.Round(0)
return now.Equal(t.Expires) || now.After(t.Expires)
}

// TokenProvider provides interface for retrieving bearer tokens.
type TokenProvider interface {
RetrieveBearerToken(aws.Context) (Token, error)
}

// TokenProviderFunc provides a helper utility to wrap a function as a type
// that implements the TokenProvider interface.
type TokenProviderFunc func(aws.Context) (Token, error)

// RetrieveBearerToken calls the wrapped function, returning the Token or
// error.
func (fn TokenProviderFunc) RetrieveBearerToken(ctx aws.Context) (Token, error) {
return fn(ctx)
}

// StaticTokenProvider provides a utility for wrapping a static bearer token
// value within an implementation of a token provider.
type StaticTokenProvider struct {
Token Token
}

// RetrieveBearerToken returns the static token specified.
func (s StaticTokenProvider) RetrieveBearerToken(aws.Context) (Token, error) {
return s.Token, nil
}
20 changes: 0 additions & 20 deletions aws/credentials/ssocreds/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
Expand Down Expand Up @@ -123,25 +122,6 @@ func getCacheFileName(url string) (string, error) {
return strings.ToLower(hex.EncodeToString(hash.Sum(nil))) + ".json", nil
}

type rfc3339 time.Time

func (r *rfc3339) UnmarshalJSON(bytes []byte) error {
var value string

if err := json.Unmarshal(bytes, &value); err != nil {
return err
}

parse, err := time.Parse(time.RFC3339, value)
if err != nil {
return fmt.Errorf("expected RFC3339 timestamp: %v", err)
}

*r = rfc3339(parse)

return nil
}

type token struct {
AccessToken string `json:"accessToken"`
ExpiresAt rfc3339 `json:"expiresAt"`
Expand Down
229 changes: 229 additions & 0 deletions aws/credentials/ssocreds/sso_cached_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package ssocreds

import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/aws/aws-sdk-go/internal/shareddefaults"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)

var resolvedOsUserHomeDir = shareddefaults.UserHomeDir

// StandardCachedTokenFilepath returns the filepath for the cached SSO token file, or
// error if unable get derive the path. Key that will be used to compute a SHA1
// value that is hex encoded.
//
// Derives the filepath using the Key as:
//
// ~/.aws/sso/cache/<sha1-hex-encoded-key>.json
func StandardCachedTokenFilepath(key string) (string, error) {
homeDir := resolvedOsUserHomeDir()
if len(homeDir) == 0 {
return "", fmt.Errorf("unable to get USER's home directory for cached token")
}
hash := sha1.New()
if _, err := hash.Write([]byte(key)); err != nil {
return "", fmt.Errorf("unable to compute cached token filepath key SHA1 hash, %v", err)
}

cacheFilename := strings.ToLower(hex.EncodeToString(hash.Sum(nil))) + ".json"

return filepath.Join(homeDir, ".aws", "sso", "cache", cacheFilename), nil
}

type tokenKnownFields struct {
AccessToken string `json:"accessToken,omitempty"`
ExpiresAt *rfc3339 `json:"expiresAt,omitempty"`

RefreshToken string `json:"refreshToken,omitempty"`
ClientID string `json:"clientId,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
}

type cachedToken struct {
tokenKnownFields
UnknownFields map[string]interface{} `json:"-"`
}

func (t cachedToken) MarshalJSON() ([]byte, error) {
fields := map[string]interface{}{}

setTokenFieldString(fields, "accessToken", t.AccessToken)
setTokenFieldRFC3339(fields, "expiresAt", t.ExpiresAt)

setTokenFieldString(fields, "refreshToken", t.RefreshToken)
setTokenFieldString(fields, "clientId", t.ClientID)
setTokenFieldString(fields, "clientSecret", t.ClientSecret)

for k, v := range t.UnknownFields {
if _, ok := fields[k]; ok {
return nil, fmt.Errorf("unknown token field %v, duplicates known field", k)
}
fields[k] = v
}

return json.Marshal(fields)
}

func setTokenFieldString(fields map[string]interface{}, key, value string) {
if value == "" {
return
}
fields[key] = value
}
func setTokenFieldRFC3339(fields map[string]interface{}, key string, value *rfc3339) {
if value == nil {
return
}
fields[key] = value
}

func (t *cachedToken) UnmarshalJSON(b []byte) error {
var fields map[string]interface{}
if err := json.Unmarshal(b, &fields); err != nil {
return nil
}

t.UnknownFields = map[string]interface{}{}

for k, v := range fields {
var err error
switch k {
case "accessToken":
err = getTokenFieldString(v, &t.AccessToken)
case "expiresAt":
err = getTokenFieldRFC3339(v, &t.ExpiresAt)
case "refreshToken":
err = getTokenFieldString(v, &t.RefreshToken)
case "clientId":
err = getTokenFieldString(v, &t.ClientID)
case "clientSecret":
err = getTokenFieldString(v, &t.ClientSecret)
default:
t.UnknownFields[k] = v
}

if err != nil {
return fmt.Errorf("field %q, %v", k, err)
}
}

return nil
}

func getTokenFieldString(v interface{}, value *string) error {
var ok bool
*value, ok = v.(string)
if !ok {
return fmt.Errorf("expect value to be string, got %T", v)
}
return nil
}

func getTokenFieldRFC3339(v interface{}, value **rfc3339) error {
var stringValue string
if err := getTokenFieldString(v, &stringValue); err != nil {
return err
}

timeValue, err := parseRFC3339(stringValue)
if err != nil {
return err
}

*value = &timeValue
return nil
}

func loadCachedToken(filename string) (cachedToken, error) {
fileBytes, err := ioutil.ReadFile(filename)
if err != nil {
return cachedToken{}, fmt.Errorf("failed to read cached SSO token file, %v", err)
}

var t cachedToken
if err := json.Unmarshal(fileBytes, &t); err != nil {
return cachedToken{}, fmt.Errorf("failed to parse cached SSO token file, %v", err)
}

if len(t.AccessToken) == 0 || t.ExpiresAt == nil || time.Time(*t.ExpiresAt).IsZero() {
return cachedToken{}, fmt.Errorf(
"cached SSO token must contain accessToken and expiresAt fields")
}

return t, nil
}

func storeCachedToken(filename string, t cachedToken, fileMode os.FileMode) (err error) {
tmpFilename := filename + ".tmp-" + strconv.FormatInt(nowTime().UnixNano(), 10)
if err := writeCacheFile(tmpFilename, fileMode, t); err != nil {
return err
}

if err := os.Rename(tmpFilename, filename); err != nil {
return fmt.Errorf("failed to replace old cached SSO token file, %v", err)
}

return nil
}

func writeCacheFile(filename string, fileMode os.FileMode, t cachedToken) (err error) {
var f *os.File
f, err = os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, fileMode)
if err != nil {
return fmt.Errorf("failed to create cached SSO token file %v", err)
}

defer func() {
closeErr := f.Close()
if err == nil && closeErr != nil {
err = fmt.Errorf("failed to close cached SSO token file, %v", closeErr)
}
}()

encoder := json.NewEncoder(f)

if err = encoder.Encode(t); err != nil {
return fmt.Errorf("failed to serialize cached SSO token, %v", err)
}

return nil
}

type rfc3339 time.Time

func (r *rfc3339) UnmarshalJSON(bytes []byte) error {
var value string
var err error

if err = json.Unmarshal(bytes, &value); err != nil {
return err
}

*r, err = parseRFC3339(value)
return err
}

func parseRFC3339(v string) (rfc3339, error) {
parsed, err := time.Parse(time.RFC3339, v)
if err != nil {
return rfc3339{}, fmt.Errorf("expected RFC3339 timestamp: %v", err)
}

return rfc3339(parsed), nil
}

func (r *rfc3339) MarshalJSON() ([]byte, error) {
value := time.Time(*r).Format(time.RFC3339)

// Use JSON unmarshal to unescape the quoted value making use of JSON's
// quoting rules.
return json.Marshal(value)
}
Loading