Skip to content

Commit

Permalink
support IAM credentials to be fully cutomizable not just via ENVs (#1910
Browse files Browse the repository at this point in the history
)

currently customizing the behavior of credentials.IAM for

- Container authorization style
- EC2 Metadata service credentials
- EKS credentials

Was via environment variables, allow this to be changed in a more
declarative manner that can be used and remembered in application.
  • Loading branch information
harshavardhana authored Dec 6, 2023
1 parent a9b7701 commit 6bc93a8
Showing 1 changed file with 78 additions and 28 deletions.
106 changes: 78 additions & 28 deletions pkg/credentials/iam_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,36 @@ type IAM struct {

// Custom endpoint to fetch IAM role credentials.
Endpoint string

// Region configurable custom region for STS
Region string

// Support for container authorization token https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html
Container struct {
AuthorizationToken string
CredentialsFullURI string
CredentialsRelativeURI string
}

// EKS based k8s RBAC authorization - https://docs.aws.amazon.com/eks/latest/userguide/pod-configuration.html
EKSIdentity struct {
TokenFile string
RoleARN string
RoleSessionName string
}
}

// IAM Roles for Amazon EC2
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
const (
defaultIAMRoleEndpoint = "http://169.254.169.254"
defaultECSRoleEndpoint = "http://169.254.170.2"
defaultSTSRoleEndpoint = "https://sts.amazonaws.com"
defaultIAMSecurityCredsPath = "/latest/meta-data/iam/security-credentials/"
tokenRequestTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
tokenPath = "/latest/api/token"
tokenTTL = "21600"
tokenRequestHeader = "X-aws-ec2-metadata-token"
DefaultIAMRoleEndpoint = "http://169.254.169.254"
DefaultECSRoleEndpoint = "http://169.254.170.2"
DefaultSTSRoleEndpoint = "https://sts.amazonaws.com"
DefaultIAMSecurityCredsPath = "/latest/meta-data/iam/security-credentials/"
TokenRequestTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
TokenPath = "/latest/api/token"
TokenTTL = "21600"
TokenRequestHeader = "X-aws-ec2-metadata-token"
)

// NewIAM returns a pointer to a new Credentials object wrapping the IAM.
Expand All @@ -84,37 +101,71 @@ func NewIAM(endpoint string) *Credentials {
// the desired
func (m *IAM) Retrieve() (Value, error) {
token := os.Getenv("AWS_CONTAINER_AUTHORIZATION_TOKEN")
if token == "" {
token = m.Container.AuthorizationToken
}

relativeURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
if relativeURI == "" {
relativeURI = m.Container.CredentialsRelativeURI
}

fullURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI")
if fullURI == "" {
fullURI = m.Container.CredentialsFullURI
}

identityFile := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")
if identityFile == "" {
identityFile = m.EKSIdentity.TokenFile
}

roleArn := os.Getenv("AWS_ROLE_ARN")
if roleArn == "" {
roleArn = m.EKSIdentity.RoleARN
}

roleSessionName := os.Getenv("AWS_ROLE_SESSION_NAME")
if roleSessionName == "" {
roleSessionName = m.EKSIdentity.RoleSessionName
}

region := os.Getenv("AWS_REGION")
if region == "" {
region = m.Region
}

var roleCreds ec2RoleCredRespBody
var err error

endpoint := m.Endpoint
switch {
case len(os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")) > 0:
case identityFile != "":
if len(endpoint) == 0 {
if len(os.Getenv("AWS_REGION")) > 0 {
if strings.HasPrefix(os.Getenv("AWS_REGION"), "cn-") {
endpoint = "https://sts." + os.Getenv("AWS_REGION") + ".amazonaws.com.cn"
if region != "" {
if strings.HasPrefix(region, "cn-") {
endpoint = "https://sts." + region + ".amazonaws.com.cn"
} else {
endpoint = "https://sts." + os.Getenv("AWS_REGION") + ".amazonaws.com"
endpoint = "https://sts." + region + ".amazonaws.com"
}
} else {
endpoint = defaultSTSRoleEndpoint
endpoint = DefaultSTSRoleEndpoint
}
}

creds := &STSWebIdentity{
Client: m.Client,
STSEndpoint: endpoint,
GetWebIDTokenExpiry: func() (*WebIdentityToken, error) {
token, err := os.ReadFile(os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE"))
token, err := os.ReadFile(identityFile)
if err != nil {
return nil, err
}

return &WebIdentityToken{Token: string(token)}, nil
},
RoleARN: os.Getenv("AWS_ROLE_ARN"),
roleSessionName: os.Getenv("AWS_ROLE_SESSION_NAME"),
RoleARN: roleArn,
roleSessionName: roleSessionName,
}

stsWebIdentityCreds, err := creds.Retrieve()
Expand All @@ -123,17 +174,16 @@ func (m *IAM) Retrieve() (Value, error) {
}
return stsWebIdentityCreds, err

case len(os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")) > 0:
case relativeURI != "":
if len(endpoint) == 0 {
endpoint = fmt.Sprintf("%s%s", defaultECSRoleEndpoint,
os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"))
endpoint = fmt.Sprintf("%s%s", DefaultECSRoleEndpoint, relativeURI)
}

roleCreds, err = getEcsTaskCredentials(m.Client, endpoint, token)

case len(os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI")) > 0:
case fullURI != "":
if len(endpoint) == 0 {
endpoint = os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI")
endpoint = fullURI
var ok bool
if ok, err = isLoopback(endpoint); !ok {
if err == nil {
Expand Down Expand Up @@ -189,7 +239,7 @@ func getIAMRoleURL(endpoint string) (*url.URL, error) {
if err != nil {
return nil, err
}
u.Path = defaultIAMSecurityCredsPath
u.Path = DefaultIAMSecurityCredsPath
return u, nil
}

Expand All @@ -203,7 +253,7 @@ func listRoleNames(client *http.Client, u *url.URL, token string) ([]string, err
return nil, err
}
if token != "" {
req.Header.Add(tokenRequestHeader, token)
req.Header.Add(TokenRequestHeader, token)
}
resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -258,11 +308,11 @@ func fetchIMDSToken(client *http.Client, endpoint string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint+tokenPath, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint+TokenPath, nil)
if err != nil {
return "", err
}
req.Header.Add(tokenRequestTTLHeader, tokenTTL)
req.Header.Add(TokenRequestTTLHeader, TokenTTL)
resp, err := client.Do(req)
if err != nil {
return "", err
Expand All @@ -285,7 +335,7 @@ func fetchIMDSToken(client *http.Client, endpoint string) (string, error) {
// reading the response an error will be returned.
func getCredentials(client *http.Client, endpoint string) (ec2RoleCredRespBody, error) {
if endpoint == "" {
endpoint = defaultIAMRoleEndpoint
endpoint = DefaultIAMRoleEndpoint
}

// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
Expand Down Expand Up @@ -332,7 +382,7 @@ func getCredentials(client *http.Client, endpoint string) (ec2RoleCredRespBody,
return ec2RoleCredRespBody{}, err
}
if token != "" {
req.Header.Add(tokenRequestHeader, token)
req.Header.Add(TokenRequestHeader, token)
}

resp, err := client.Do(req)
Expand Down

0 comments on commit 6bc93a8

Please sign in to comment.