Skip to content

Commit

Permalink
Update app label value (aws#28)
Browse files Browse the repository at this point in the history
`eks-pod-identiy-agent` -> `eks-pod-identity-agent`

fix: fix metrics service selector (aws#24)

fix: tidy go mods to fix vendoring (aws#25)

Update app label value (aws#28)

`eks-pod-identiy-agent` -> `eks-pod-identity-agent`

Initial service account fetcher logic

Add `kubernetes.io` parsing logic
  • Loading branch information
carise authored and taraspos committed Oct 18, 2024
1 parent 60a37c4 commit 48d7d50
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 133 deletions.
2 changes: 1 addition & 1 deletion charts/eks-pod-identity-agent/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ helm.sh/chart: {{ include "eks-pod-identity-agent.chart" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app: eks-pod-identiy-agent
app: eks-pod-identity-agent
{{- end }}

{{/*
Expand Down
35 changes: 32 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ require (
go.uber.org/mock v0.3.0
golang.org/x/sys v0.25.0
golang.org/x/time v0.3.0
k8s.io/api v0.31.1
k8s.io/apimachinery v0.31.1
k8s.io/client-go v0.31.1
)

require (
Expand All @@ -33,21 +36,47 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
123 changes: 112 additions & 11 deletions go.sum

Large diffs are not rendered by default.

109 changes: 36 additions & 73 deletions pkg/extensions/chainrole/chainrole.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,47 @@ import (

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/config"
awsCreds "github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/eks"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/aws-sdk-go-v2/service/sts/types"
"github.com/golang-jwt/jwt/v5"
"github.com/sirupsen/logrus"
"go.amzn.com/eks/eks-pod-identity-agent/internal/middleware/logger"
"go.amzn.com/eks/eks-pod-identity-agent/pkg/credentials"
"go.amzn.com/eks/eks-pod-identity-agent/pkg/extensions/chainrole/ekspodidentities"
"go.amzn.com/eks/eks-pod-identity-agent/pkg/extensions/chainrole/serviceaccount"
)

const (
assumeRoleAnnotationPrefix = "assume-role.ekspia.go.amzn.com/"
sessionTagRoleAnnotationPrefix = assumeRoleAnnotationPrefix + "session-tag/"
// service account annotations doesn't support more than one "/"
sessionTagRoleAnnotationPrefix2 = assumeRoleAnnotationPrefix + "session-tag-"
)

type (
roleAssumer interface {
AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error)
}

sessionConfigFunc func(ctx context.Context, awsCfg aws.Config, clusterName string, associationID string) (*sts.AssumeRoleInput, error)
sessionConfigRetriever interface {
GetSessionConfigMap(ctx context.Context, request *credentials.EksCredentialsRequest) (map[string]string, error)
}

CredentialRetriever struct {
delegate credentials.CredentialRetriever
jwtParser *jwt.Parser
roleAssumer roleAssumer
getSessionConfig sessionConfigFunc
sessionConfigRetriever sessionConfigRetriever
reNamespaceFilter *regexp.Regexp
reServiceAccountFilter *regexp.Regexp
}
)

func NewCredentialsRetriever(awsCfg aws.Config, eksCredentialsRetriever credentials.CredentialRetriever) *CredentialRetriever {
cr := &CredentialRetriever{
delegate: eksCredentialsRetriever,
jwtParser: jwt.NewParser(),
roleAssumer: sts.NewFromConfig(awsCfg),
getSessionConfig: getSessionConfigurationFromEKSPodIdentityTags,
delegate: eksCredentialsRetriever,
jwtParser: jwt.NewParser(),
roleAssumer: sts.NewFromConfig(awsCfg),
}

log := logger.FromContext(context.TODO()).WithField("extension", "chainrole")
Expand All @@ -69,75 +71,46 @@ func NewCredentialsRetriever(awsCfg aws.Config, eksCredentialsRetriever credenti
log.Info("Enabled extension...")
}

return cr
}

func getSessionConfigurationFromEKSPodIdentityTags(ctx context.Context, awsCfg aws.Config, clusterName, associationID string) (*sts.AssumeRoleInput, error) {
// Describe pod identity association to get tags
podIdentityAssociation, err := eks.NewFromConfig(awsCfg).DescribePodIdentityAssociation(ctx,
&eks.DescribePodIdentityAssociationInput{
AssociationId: aws.String(associationID),
ClusterName: aws.String(clusterName),
})
if err != nil {
return nil, fmt.Errorf("error describing pod identity association %s/%s: %w", clusterName, associationID, err)
switch sessionConfigSourceVal {
case eksPodIdentityAssociationTags:
cr.sessionConfigRetriever = ekspodidentities.NewSessionConfigRetriever(eksCredentialsRetriever)
case serviceAccountAnnotations:
cr.sessionConfigRetriever = serviceaccount.NewSessionConfigRetriever()
default:
}

assumeRoleInput := tagsToSTSAssumeRole(podIdentityAssociation.Association.Tags)

if assumeRoleInput.RoleArn == nil {
return nil, fmt.Errorf("couldn't get assume role arn from pod identity association tags %v", podIdentityAssociation.Association.Tags)
}

return assumeRoleInput, nil
return cr
}

func (c *CredentialRetriever) GetIamCredentials(ctx context.Context, request *credentials.EksCredentialsRequest) (
*credentials.EksCredentialsResponse, credentials.ResponseMetadata, error) {
log := logger.FromContext(ctx).WithField("extension", "chainrole")

// Get AWS EKS Pod Identity credentials as usual
iamCredentials, responseMetadata, err := c.delegate.GetIamCredentials(ctx, request)
if err != nil {
return nil, nil, err
}

// Get Namespace and ServiceAccount names from JWT token
ns, sa, err := c.serviceAccountFromJWT(request.ServiceAccountToken)
if err != nil {
return nil, nil, fmt.Errorf("error parsing JWT token: %w", err)
}

log = log.WithFields(logrus.Fields{
"namespace": ns,
"serviceaccount": sa,
"cluster-name": request.ClusterName,
"association-id": responseMetadata.AssociationId(),
})

// Check if Namespace/ServiceAccount filters configured
// and do not proceed with role chaining if they don't match
if !c.isEnabledFor(ns, sa) {
log.Debug("namespace/serviceaccount do not match ChainRole filter. Skipping role chaining")
return iamCredentials, responseMetadata, nil
return c.delegate.GetIamCredentials(ctx, request)
}

// Assume eks pod identity credentials
podIdentityCfg, err := config.LoadDefaultConfig(context.TODO(), config.WithCredentialsProvider(
awsCreds.NewStaticCredentialsProvider(iamCredentials.AccessKeyId, iamCredentials.SecretAccessKey, iamCredentials.Token),
))
if err != nil {
return nil, nil, fmt.Errorf("error loading pod identity credentials: %w", err)
}
log = log.WithFields(logrus.Fields{
"namespace": ns,
"serviceaccount": sa,
"cluster-name": request.ClusterName,
})

// Assume new session based on the configurations provided in tags
// session is assumed based on the IRSA credentials and NOT EKS Identity credentials
// this is because EKS Identity credentials adds bunch of default tags
// leaving no space for our custom tags https://github.com/aws/containers-roadmap/issues/2413
assumeRoleInput, err := c.getSessionConfig(ctx, podIdentityCfg, request.ClusterName, responseMetadata.AssociationId())
sessionConfigMap, err := c.sessionConfigRetriever.GetSessionConfigMap(ctx, request)
if err != nil {
return nil, nil, fmt.Errorf("error getting session configuration: %w", err)
return nil, nil, err
}

assumeRoleInput := tagsToSTSAssumeRole(sessionConfigMap)
assumeRoleOutput, err := c.roleAssumer.AssumeRole(ctx, assumeRoleInput)
if err != nil {
return nil, nil, fmt.Errorf("error assuming role %s: %w", *assumeRoleInput.RoleArn, err)
Expand All @@ -154,7 +127,7 @@ func (c *CredentialRetriever) GetIamCredentials(ctx context.Context, request *cr
return nil, nil, fmt.Errorf("error formatting IAM credentials: %w", err)
}

return assumedCredentials, responseMetadata, nil
return assumedCredentials, nil, nil
}

func (c *CredentialRetriever) isEnabledFor(namespace, serviceAccount string) bool {
Expand Down Expand Up @@ -196,8 +169,9 @@ func tagsToSTSAssumeRole(tags map[string]string) *sts.AssumeRoleInput {
assumeRoleParams.DurationSeconds = aws.Int32(int32(duration.Seconds()))
}

if strings.HasPrefix(key, sessionTagRoleAnnotationPrefix) {
if strings.HasPrefix(key, sessionTagRoleAnnotationPrefix) || strings.HasPrefix(key, sessionTagRoleAnnotationPrefix2) {
tagKey := strings.TrimPrefix(key, sessionTagRoleAnnotationPrefix)
tagKey = strings.TrimPrefix(tagKey, sessionTagRoleAnnotationPrefix2)

assumeRoleParams.Tags = append(assumeRoleParams.Tags, types.Tag{
Key: aws.String(tagKey),
Expand Down Expand Up @@ -228,26 +202,15 @@ func formatIAMCredentials(o *sts.AssumeRoleOutput) (*credentials.EksCredentialsR
}, nil
}

func (c *CredentialRetriever) serviceAccountFromJWT(token string) (string, string, error) {
parsedToken, _, err := c.jwtParser.ParseUnverified(token, &jwt.RegisteredClaims{})
func (c *CredentialRetriever) serviceAccountFromJWT(token string) (ns string, sa string, err error) {
claims, subject, err := serviceaccount.ServiceAccountFromJWT(token)
if err != nil {
return "", "", fmt.Errorf("error parsing JWT token: %w", err)
}

subject, err := parsedToken.Claims.GetSubject()
if err != nil {
return "", "", fmt.Errorf("error reading JWT token subject: %w", err)
}

// subject is in the format: system:serviceaccount:<namespace>:<service_account>
if !strings.HasPrefix(subject, "system:serviceaccount:") {
return "", "", errors.New("JWT token claim subject doesn't start with 'system:serviceaccount:'")
}

subjectParts := strings.Split(subject, ":")
if len(subjectParts) < 4 {
return "", "", errors.New("invalid JWT token claim subject")
if claims != nil && claims.Namespace != "" && claims.ServiceAccount.Name != "" {
return claims.Namespace, claims.ServiceAccount.Name, nil
}

return subjectParts[2], subjectParts[3], nil
return serviceaccount.ServiceAccountFromJWTSubject(subject)
}
Loading

0 comments on commit 48d7d50

Please sign in to comment.