Skip to content

Commit

Permalink
add web identity token configuration to ProviderConfig spec
Browse files Browse the repository at this point in the history
Signed-off-by: Erhan Cagirici <[email protected]>
  • Loading branch information
erhancagirici committed Feb 15, 2024
1 parent 90e9143 commit 8abf565
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 7 deletions.
24 changes: 24 additions & 0 deletions apis/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,30 @@ type AssumeRoleWithWebIdentityOptions struct {
// RoleSessionName is the session name, if you wish to uniquely identify this session.
// +optional
RoleSessionName string `json:"roleSessionName,omitempty"`

// TokenConfig is the Web Identity Token config to assume the role.
// +optional
TokenConfig *WebIdentityTokenConfig `json:"tokenConfig,omitempty"`
}

// WebIdentityTokenConfig is for configuring the token
// to be used for Web Identity authentication
//
// TODO: can be later expanded to use by inlining v1.CommonCredentialSelectors,
// Env configuration is intentionally left out to not cause ambiguity
// with the deprecated direct configuration with environment variables.
type WebIdentityTokenConfig struct {
// Source is the source of the web identity token.
// +kubebuilder:validation:Enum=Secret;Filesystem
Source xpv1.CredentialsSource `json:"source"`
// A SecretRef is a reference to a secret key that contains the credentials
// that must be used to obtain the web identity token.
// +optional
SecretRef *xpv1.SecretKeySelector `json:"secretRef,omitempty"`
// Fs is a reference to a filesystem location that contains credentials that
// must be used to obtain the web identity token.
// +optional
Fs *xpv1.FsSelector `json:"fs,omitempty"`
}

// Upbound defines the options for authenticating using Upbound as an identity
Expand Down
31 changes: 31 additions & 0 deletions apis/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: webidentity-example
spec:
credentials:
source: WebIdentity
webIdentity:
roleARN: arn:aws:iam::123456789012:role/providerexamplerole
tokenConfig:
source: Secret
secretRef:
key: token
name: example-web-identity-token-secret
namespace: upbound-system
19 changes: 17 additions & 2 deletions internal/clients/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"unsafe"

"github.com/aws/aws-sdk-go-v2/aws"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/crossplane/upjet/pkg/terraform"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand All @@ -34,6 +35,7 @@ const (
keyRoleArn = "role_arn"
keySessionName = "session_name"
keyWebIdentityTokenFile = "web_identity_token_file"
keyWebIdentityToken = "web_identity_token"
keySkipCredsValidation = "skip_credentials_validation"
keyS3UsePathStyle = "s3_use_path_style"
keySkipMetadataApiCheck = "skip_metadata_api_check"
Expand Down Expand Up @@ -113,8 +115,21 @@ func pushDownTerraformSetupBuilder(ctx context.Context, c client.Client, pc *v1b
return errors.New(`spec.credentials.webIdentity of ProviderConfig cannot be nil when the credential source is "WebIdentity"`)
}
webIdentityConfig := map[string]any{
keyRoleArn: aws.ToString(pc.Spec.Credentials.WebIdentity.RoleARN),
keyWebIdentityTokenFile: os.Getenv(envWebIdentityTokenFile),
keyRoleArn: aws.ToString(pc.Spec.Credentials.WebIdentity.RoleARN),
}
if pc.Spec.Credentials.WebIdentity.TokenConfig != nil {
tokenSelector := xpv1.CommonCredentialSelectors{
Fs: pc.Spec.Credentials.WebIdentity.TokenConfig.Fs,
SecretRef: pc.Spec.Credentials.WebIdentity.TokenConfig.SecretRef,
}
creds, err := resource.CommonCredentialExtractor(ctx, pc.Spec.Credentials.WebIdentity.TokenConfig.Source, c, tokenSelector)
if err != nil {
return errors.Wrap(err, "cannot extract token")
}
webIdentityConfig[keyWebIdentityToken] = string(creds)
} else {
// fallback to deprecated behavior with environment variables
webIdentityConfig[keyWebIdentityTokenFile] = os.Getenv(envWebIdentityTokenFile)
}
if pc.Spec.Credentials.WebIdentity.RoleSessionName != "" {
webIdentityConfig[keySessionName] = pc.Spec.Credentials.WebIdentity.RoleSessionName
Expand Down
88 changes: 83 additions & 5 deletions internal/clients/provider_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/pkg/resource"

Expand All @@ -44,6 +45,7 @@ const (
authKeySecret = "Secret"

envWebIdentityTokenFile = "AWS_WEB_IDENTITY_TOKEN_FILE"
envWebIdentityRoleARN = "AWS_ROLE_ARN"
errRoleChainConfig = "failed to load assumed role AWS config"
errAWSConfig = "failed to get AWS config"
errAWSConfigIRSA = "failed to get AWS config using IAM Roles for Service Accounts"
Expand Down Expand Up @@ -110,7 +112,7 @@ func GetAWSConfig(ctx context.Context, c client.Client, mg resource.Managed) (*a
return nil, errors.Wrap(err, errAWSConfigIRSA)
}
case authKeyWebIdentity:
cfg, err = UseWebIdentityToken(ctx, region, &pc.Spec)
cfg, err = UseWebIdentityToken(ctx, region, &pc.Spec, c)
if err != nil {
return nil, errors.Wrap(err, errAWSConfigWebIdentity)
}
Expand Down Expand Up @@ -318,6 +320,26 @@ func GetAssumeRoleWithWebIdentityConfig(ctx context.Context, cfg *aws.Config, we
return &awsConfig, nil
}

// GetAssumeRoleWithWebIdentityConfigViaTokenRetriever returns an aws.Config capable of doing
// AssumeRoleWithWebIdentity using the token obtained from the supplied stscreds.IdentityTokenRetriever.
func GetAssumeRoleWithWebIdentityConfigViaTokenRetriever(ctx context.Context, cfg *aws.Config, webID v1beta1.AssumeRoleWithWebIdentityOptions, tokenRetriever stscreds.IdentityTokenRetriever) (*aws.Config, error) {
stsclient := sts.NewFromConfig(*cfg, stsRegionOrDefault(cfg.Region))
awsConfig, err := config.LoadDefaultConfig(
ctx,
userAgentV2,
config.WithRegion(cfg.Region),
config.WithCredentialsProvider(aws.NewCredentialsCache(
stscreds.NewWebIdentityRoleProvider(
stsclient,
aws.ToString(webID.RoleARN),
tokenRetriever,
SetWebIdentityRoleOptions(webID),
)),
),
)
return &awsConfig, errors.Wrap(err, "failed to assume role via web identity")
}

// UseDefault loads the default AWS config with the specified region.
func UseDefault(ctx context.Context, region string) (*aws.Config, error) {
if region == GlobalRegion {
Expand All @@ -338,18 +360,74 @@ func UseDefault(ctx context.Context, region string) (*aws.Config, error) {
return &cfg, nil
}

type xpWebIdentityTokenRetriever struct {
ctx context.Context
kube client.Client
tokenSource v1.CredentialsSource
tokenSelector v1.CommonCredentialSelectors
}

func (x *xpWebIdentityTokenRetriever) GetIdentityToken() ([]byte, error) {
token, err := resource.CommonCredentialExtractor(x.ctx, x.tokenSource, x.kube, x.tokenSelector)
return token, errors.Wrap(err, "could not extract token from tokenSource")
}

// UseWebIdentityToken calls sts.AssumeRoleWithWebIdentity using
// the configuration supplied in ProviderConfig's
// spec.credentials.assumeRoleWithWebIdentity.
func UseWebIdentityToken(ctx context.Context, region string, pcs *v1beta1.ProviderConfigSpec) (*aws.Config, error) {
func UseWebIdentityToken(ctx context.Context, region string, pcs *v1beta1.ProviderConfigSpec, kube client.Client) (*aws.Config, error) {
if pcs.Credentials.WebIdentity == nil {
return nil, errors.New(`spec.credentials.webIdentity of ProviderConfig cannot be nil when the credential source is "WebIdentity"`)
}

// this is to preserve backward compatibility with
// 0.x providers working with >=1.x ProviderConfig API
// TODO: when configuring via AWS environment variable support is removed
// tokenConfig should be mandatory and this should return an error
if pcs.Credentials.WebIdentity.TokenConfig == nil {
cfg, err := UseDefault(ctx, region)
if err != nil {
return nil, errors.Wrap(err, "failed to get default AWS config")
}
return GetAssumeRoleWithWebIdentityConfig(ctx, cfg, *pcs.Credentials.WebIdentity, os.Getenv(envWebIdentityTokenFile))
}

// new behavior with tokenConfig in
// spec.credentials.webIdentity.tokenConfig
// the new behavior with tokenConfig does not rely on
// the AWS environment variables AWS_WEB_IDENTITY_TOKEN_FILE
// and AWS_ROLE_ARN.
// However, we start by constructing a default AWS config and
// AWS SDK enforces that when AWS_WEB_IDENTITY_TOKEN_FILE environment
// variable is set AWS_ROLE_ARN must be present.
// Otherwise, constructing the default AWS config fails.
// Hence, either both env vars must be set
// (to support the case where the controller pod has extra AWS IRSA config
// which should be automatically injecting AWS_WEB_IDENTITY_TOKEN_FILE
// and AWS_ROLE_ARN environment variables already)
// or AWS_WEB_IDENTITY_TOKEN_FILE must not exist at all.
_, foundTokenEnv := os.LookupEnv(envWebIdentityTokenFile)
_, foundRoleArnEnv := os.LookupEnv(envWebIdentityRoleARN)
if foundTokenEnv && !foundRoleArnEnv {
return nil, errors.Errorf("if you intend to use IRSA together with WebIdentity auth, environment variable %s must be set together with %s. If only WebIdentity auth without any IRSA configuration is intended, %s must be unset",
envWebIdentityRoleARN, envWebIdentityTokenFile, envWebIdentityTokenFile)
}

cfg, err := UseDefault(ctx, region)
if err != nil {
return nil, errors.Wrap(err, "failed to get default AWS config")
}
if pcs.Credentials.WebIdentity == nil {
return nil, errors.New(`spec.credentials.webIdentity of ProviderConfig cannot be nil when the credential source is "WebIdentity"`)
tokenRetriever := &xpWebIdentityTokenRetriever{
ctx: ctx,
kube: kube,
tokenSource: pcs.Credentials.WebIdentity.TokenConfig.Source,
tokenSelector: v1.CommonCredentialSelectors{
Fs: pcs.Credentials.WebIdentity.TokenConfig.Fs,
SecretRef: pcs.Credentials.WebIdentity.TokenConfig.SecretRef,
},
}
return GetAssumeRoleWithWebIdentityConfig(ctx, cfg, *pcs.Credentials.WebIdentity, os.Getenv(envWebIdentityTokenFile))

return GetAssumeRoleWithWebIdentityConfigViaTokenRetriever(ctx, cfg, *pcs.Credentials.WebIdentity, tokenRetriever)
}

// UseUpbound calls sts.AssumeRoleWithWebIdentity using the configuration
Expand Down
88 changes: 88 additions & 0 deletions package/crds/aws.upbound.io_providerconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,50 @@ spec:
description: RoleSessionName is the session name, if you
wish to uniquely identify this session.
type: string
tokenConfig:
description: TokenConfig is the Web Identity Token config
to assume the role.
properties:
fs:
description: Fs is a reference to a filesystem location
that contains credentials that must be used to obtain
the web identity token.
properties:
path:
description: Path is a filesystem path.
type: string
required:
- path
type: object
secretRef:
description: A SecretRef is a reference to a secret
key that contains the credentials that must be used
to obtain the web identity token.
properties:
key:
description: The key to select.
type: string
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- key
- name
- namespace
type: object
source:
description: Source is the source of the web identity
token.
enum:
- Secret
- Filesystem
type: string
required:
- source
type: object
type: object
type: object
webIdentity:
Expand All @@ -170,6 +214,50 @@ spec:
description: RoleSessionName is the session name, if you wish
to uniquely identify this session.
type: string
tokenConfig:
description: TokenConfig is the Web Identity Token config
to assume the role.
properties:
fs:
description: Fs is a reference to a filesystem location
that contains credentials that must be used to obtain
the web identity token.
properties:
path:
description: Path is a filesystem path.
type: string
required:
- path
type: object
secretRef:
description: A SecretRef is a reference to a secret key
that contains the credentials that must be used to obtain
the web identity token.
properties:
key:
description: The key to select.
type: string
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- key
- name
- namespace
type: object
source:
description: Source is the source of the web identity
token.
enum:
- Secret
- Filesystem
type: string
required:
- source
type: object
type: object
required:
- source
Expand Down

0 comments on commit 8abf565

Please sign in to comment.