-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
auth.go
131 lines (109 loc) · 4.71 KB
/
auth.go
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
package aws
import (
"context"
"fmt"
"os"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/pquerna/otp/totp"
)
const (
AuthAssumeRoleEnvVar = "TERRATEST_IAM_ROLE" // OS environment variable name through which Assume Role ARN may be passed for authentication
)
// NewAuthenticatedSession creates an AWS Config following to standard AWS authentication workflow.
// If AuthAssumeIamRoleEnvVar environment variable is set, assumes IAM role specified in it.
func NewAuthenticatedSession(region string) (*aws.Config, error) {
if assumeRoleArn, ok := os.LookupEnv(AuthAssumeRoleEnvVar); ok {
return NewAuthenticatedSessionFromRole(region, assumeRoleArn)
} else {
return NewAuthenticatedSessionFromDefaultCredentials(region)
}
}
// NewAuthenticatedSessionFromDefaultCredentials gets an AWS Config, checking that the user has credentials properly configured in their environment.
func NewAuthenticatedSessionFromDefaultCredentials(region string) (*aws.Config, error) {
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region))
if err != nil {
return nil, CredentialsError{UnderlyingErr: err}
}
return &cfg, nil
}
// NewAuthenticatedSessionFromRole returns a new AWS Config after assuming the
// role whose ARN is provided in roleARN. If the credentials are not properly
// configured in the underlying environment, an error is returned.
func NewAuthenticatedSessionFromRole(region string, roleARN string) (*aws.Config, error) {
cfg, err := NewAuthenticatedSessionFromDefaultCredentials(region)
if err != nil {
return nil, err
}
client := sts.NewFromConfig(*cfg)
roleProvider := stscreds.NewAssumeRoleProvider(client, roleARN)
retrieve, err := roleProvider.Retrieve(context.Background())
if err != nil {
return nil, CredentialsError{UnderlyingErr: err}
}
return &aws.Config{
Region: region,
Credentials: aws.NewCredentialsCache(credentials.StaticCredentialsProvider{
Value: retrieve,
}),
}, nil
}
// CreateAwsSessionWithCreds creates a new AWS Config using explicit credentials. This is useful if you want to create an IAM User dynamically and
// create an AWS Config authenticated as the new IAM User.
func CreateAwsSessionWithCreds(region string, accessKeyID string, secretAccessKey string) (*aws.Config, error) {
return &aws.Config{
Region: region,
Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, "")),
}, nil
}
// CreateAwsSessionWithMfa creates a new AWS Config authenticated using an MFA token retrieved using the given STS client and MFA Device.
func CreateAwsSessionWithMfa(region string, stsClient *sts.Client, mfaDevice *types.VirtualMFADevice) (*aws.Config, error) {
tokenCode, err := GetTimeBasedOneTimePassword(mfaDevice)
if err != nil {
return nil, err
}
output, err := stsClient.GetSessionToken(context.Background(), &sts.GetSessionTokenInput{
SerialNumber: mfaDevice.SerialNumber,
TokenCode: aws.String(tokenCode),
})
if err != nil {
return nil, err
}
accessKeyID := *output.Credentials.AccessKeyId
secretAccessKey := *output.Credentials.SecretAccessKey
sessionToken := *output.Credentials.SessionToken
return &aws.Config{
Region: region,
Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, sessionToken)),
}, nil
}
// GetTimeBasedOneTimePassword gets a One-Time Password from the given mfaDevice. Per the RFC 6238 standard, this value will be different every 30 seconds.
func GetTimeBasedOneTimePassword(mfaDevice *types.VirtualMFADevice) (string, error) {
base32StringSeed := string(mfaDevice.Base32StringSeed)
otp, err := totp.GenerateCode(base32StringSeed, time.Now())
if err != nil {
return "", err
}
return otp, nil
}
// ReadPasswordPolicyMinPasswordLength returns the minimal password length.
func ReadPasswordPolicyMinPasswordLength(iamClient *iam.Client) (int, error) {
output, err := iamClient.GetAccountPasswordPolicy(context.Background(), &iam.GetAccountPasswordPolicyInput{})
if err != nil {
return -1, err
}
return int(*output.PasswordPolicy.MinimumPasswordLength), nil
}
// CredentialsError is an error that occurs because AWS credentials can't be found.
type CredentialsError struct {
UnderlyingErr error
}
func (err CredentialsError) Error() string {
return fmt.Sprintf("Error finding AWS credentials. Did you set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables or configure an AWS profile? Underlying error: %v", err.UnderlyingErr)
}