Skip to content

Commit

Permalink
Added AWS_STS_REGIONAL_ENDPOINTS flag/annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
micahhausler committed Oct 17, 2020
1 parent c0431e1 commit e95bb68
Show file tree
Hide file tree
Showing 25 changed files with 727 additions and 447 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ deploy/deployment.yaml
build
/certs/
SAMToolkit.*
coverage.out
13 changes: 8 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ include ${BGO_MAKEFILE}
export CGO_ENABLED=0
export T=github.com/aws/amazon-eks-pod-identity-webhook
UNAME_S = $(shell uname -s)
GO_INSTALL_FLAGS = -ldflags="-s -w"
GO_LDFLAGS = -ldflags='-s -w -buildid=""'

install:: build
ifeq ($(UNAME_S), Darwin)
GOOS=darwin GOARCH=amd64 go build -o build/gopath/bin/darwin_amd64/amazon-eks-pod-identity-webhook $(GO_INSTALL_FLAGS) $V $T
GOOS=darwin GOARCH=amd64 go build -o build/gopath/bin/darwin_amd64/amazon-eks-pod-identity-webhook $(GO_LDFLAGS) $V $T
endif
GOOS=linux GOARCH=amd64 go build -o build/gopath/bin/linux_amd64/amazon-eks-pod-identity-webhook $(GO_INSTALL_FLAGS) $V $T

GOOS=linux GOARCH=amd64 go build -o build/gopath/bin/linux_amd64/amazon-eks-pod-identity-webhook $(GO_LDFLAGS) $V $T

# Generic make
REGISTRY_ID?=602401143452
IMAGE_NAME?=eks/pod-identity-webhook
REGION?=us-west-2
IMAGE?=$(REGISTRY_ID).dkr.ecr.$(REGION).amazonaws.com/$(IMAGE_NAME)

test:
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

docker:
@echo 'Building image $(IMAGE)...'
docker build --no-cache -t $(IMAGE) .
Expand Down Expand Up @@ -94,7 +97,7 @@ delete-config:

clean::
rm -rf ./amazon-eks-pod-identity-webhook
rm -rf ./certs/
rm -rf ./certs/ coverage.out

.PHONY: docker push build local-serve local-request cluster-up cluster-down prep-config deploy-config delete-config clean

Expand Down
41 changes: 34 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ This webhook is for mutating pods that will require AWS IAM access.
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::111122223333:oidc-provider/oidc.us-west-2.eks.amazonaws.com/624a142e-43fc-4a4e-9a65-0adbfe9d6a85"
"Federated": "arn:aws:iam::111122223333:oidc-provider/oidc.REGION.eks.amazonaws.com/CLUSTER_ID"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"__doc_comment": "scope the role to the service account (optional)",
"StringEquals": {
"oidc.us-west-2.eks.amazonaws.com/624a142e-43fc-4a4e-9a65-0adbfe9d6a85:sub": "system:serviceaccount:default:my-serviceaccount"
"oidc.REGION.eks.amazonaws.com/CLUSTER_ID:sub": "system:serviceaccount:default:my-serviceaccount"
},
"__doc_comment": "scope the role to a namespace (optional)",
"StringLike": {
"oidc.us-west-2.eks.amazonaws.com/624a142e-43fc-4a4e-9a65-0adbfe9d6a85:sub": "system:serviceaccount:default:*"
"oidc.REGION.eks.amazonaws.com/CLUSTER_ID:sub": "system:serviceaccount:default:*"
}
}
}
Expand All @@ -48,6 +48,11 @@ This webhook is for mutating pods that will require AWS IAM access.
namespace: default
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::111122223333:role/s3-reader"
# optional: Defaults to "sts.amazonaws.com" if not set
eks.amazonaws.com/audience: "sts.amazonaws.com"
# optional: When set to "true", adds AWS_STS_REGIONAL_ENDPOINTS env var
# to containers
eks.amazonaws.com/sts-regional-endpoints: "true"
```
4. All new pod pods launched using this Service Account will be modified to use
IAM for pods. Below is an example pod spec with the environment variables and
Expand All @@ -58,9 +63,18 @@ This webhook is for mutating pods that will require AWS IAM access.
metadata:
name: my-pod
namespace: default
annotations:
# optional: A comma-separated list of initContainers and container names
# to skip adding volumes and environemnt variables
eks.amazonaws.com/skip-containers: "init-first,sidecar"
spec:
serviceAccountName: my-serviceaccount
initContainers:
- name: init-first
image: container-image:version
containers:
- name: sidecar
image: container-image:version
- name: container-name
image: container-image:version
### Everything below is added by the webhook ###
Expand All @@ -73,6 +87,8 @@ This webhook is for mutating pods that will require AWS IAM access.
value: "arn:aws:iam::111122223333:role/s3-reader"
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
- name: AWS_STS_REGIONAL_ENDPOINTS
value: "regional"
volumeMounts:
- mountPath: "/var/run/secrets/eks.amazonaws.com/serviceaccount/"
name: aws-token
Expand All @@ -85,7 +101,7 @@ This webhook is for mutating pods that will require AWS IAM access.
expirationSeconds: 86400
path: token
```

### Usage with Windows container workloads

To ensure workloads are scheduled on windows nodes have the right environment variables, they must have a `nodeSelector` targeting windows it must run on. Workloads targeting windows nodes using `nodeAffinity` are currently not supported.
Expand Down Expand Up @@ -121,12 +137,14 @@ Usage of amazon-eks-pod-identity-webhook:
--log_file string If non-empty, use this log file
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
--logtostderr log to standard error instead of files (default true)
--metrics-port int Port to listen on for metrics and healthz (http) (default 9999)
--namespace string (in-cluster) The namespace name this webhook and the tls secret resides in (default "eks")
--port int Port to listen on (default 443)
--service-name string (in-cluster) The service name fronting this webhook (default "pod-identity-webhook")
--skip_headers If true, avoid header prefixes in the log messages
--skip_log_headers If true, avoid headers when openning log files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
--sts-regional-endpoint false Whether to inject the AWS_STS_REGIONAL_ENDPOINTS=regional env var in mutated pods. Defaults to false.
--tls-cert string (out-of-cluster) TLS certificate file path (default "/etc/webhook/certs/tls.crt")
--tls-key string (out-of-cluster) TLS key file path (default "/etc/webhook/certs/tls.key")
--tls-secret string (in-cluster) The secret name for storing the TLS serving cert (default "pod-identity-webhook")
Expand All @@ -142,6 +160,18 @@ Usage of amazon-eks-pod-identity-webhook:
When the `aws-default-region` flag is set this webhook will inject `AWS_DEFAULT_REGION` and `AWS_REGION` in mutated containers if `AWS_DEFAULT_REGION` and `AWS_REGION` are not already set.
### AWS_STS_REGIONAL_ENDPOINTS Injection
When the `sts-regional-endpoint` flag is set to `true`, the webhook will
inject the environment variable `AWS_STS_REGIONAL_ENDPOINTS` with the value set
to `regional`. This environment variable will configure the AWS SDKs to perform
the `sts:AssumeRoleWithWebIdentity` call to get credentials from the regional
endpoint, instead of the global endpoint in `us-east-1`. This is desirable in
almost all cases, unless the STS regional endpoint is [disabled in your
account](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html).
You can also enable this per-service account with the annotation
`eks.amazonaws.com/sts-regional-endpoint` set to `"true"`.
## Container Images
Expand All @@ -167,9 +197,6 @@ For self-hosted API server configuration, see see [SELF_HOSTED_SETUP.md](/SELF_H
### On API server
TODO
## Development
TODO
## Code of Conduct
See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ require (
github.com/json-iterator/go v1.1.6 // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/pkg/errors v0.8.0
github.com/prometheus/client_golang v0.9.3
github.com/spf13/pflag v1.0.3
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/square/go-jose.v2 v2.5.0
k8s.io/api v0.0.0-20190606204050-af9c91bd2759
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
k8s.io/client-go v11.0.1-0.20190606204521-b8faab9c5193+incompatible
k8s.io/klog v0.3.0
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 // indirect
k8s.io/kubernetes v1.14.3
k8s.io/utils v0.0.0-20190529001817-6999998975a7 // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
sigs.k8s.io/yaml v1.1.0
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/square/go-jose.v2 v2.5.0 h1:OZ4sdq+Y+SHfYB7vfthi1Ei8b0vkP8ZPQgUfUwdUSqo=
gopkg.in/square/go-jose.v2 v2.5.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.0.0-20190606204050-af9c91bd2759 h1:T8xTLSBgKsq1bkiAwG9xamEydWVpBv9fHl5S/TDh3OU=
Expand Down
9 changes: 6 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func main() {
mountPath := flag.String("token-mount-path", "/var/run/secrets/eks.amazonaws.com/serviceaccount", "The path to mount tokens")
tokenExpiration := flag.Int64("token-expiration", 86400, "The token expiration")
region := flag.String("aws-default-region", "", "If set, AWS_DEFAULT_REGION and AWS_REGION will be set to this value in mutated containers")
regionalSTS := flag.Bool("sts-regional-endpoint", false, "Whether to inject the AWS_STS_REGIONAL_ENDPOINTS=regional env var in mutated pods. Defaults to `false`.")

version := flag.Bool("version", false, "Display the version and exit")

Expand Down Expand Up @@ -96,15 +97,18 @@ func main() {
saCache := cache.New(
*audience,
*annotationPrefix,
*regionalSTS,
clientset,
)
saCache.Start()

mod := handler.NewModifier(
handler.WithAnnotationDomain(*annotationPrefix),
handler.WithExpiration(*tokenExpiration),
handler.WithMountPath(*mountPath),
handler.WithServiceAccountCache(saCache),
handler.WithRegion(*region),
handler.WithRegionalSTS(*regionalSTS),
)

addr := fmt.Sprintf(":%d", *port)
Expand All @@ -124,7 +128,6 @@ func main() {
fmt.Fprintf(w, "ok")
})


tlsConfig := &tls.Config{}

if *inCluster {
Expand Down Expand Up @@ -180,8 +183,8 @@ func main() {
handler.ShutdownOnTerm(server, time.Duration(10)*time.Second)

metricsServer := &http.Server{
Addr: metricsAddr,
Handler: metricsMux,
Addr: metricsAddr,
Handler: metricsMux,
}

go func() {
Expand Down
58 changes: 36 additions & 22 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ package cache

import (
"fmt"
"strconv"
"sync"
"time"

v1 "k8s.io/api/core/v1"
"github.com/aws/amazon-eks-pod-identity-webhook/pkg"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
Expand All @@ -30,32 +32,34 @@ import (
)

type CacheResponse struct {
RoleARN string
Audience string
RoleARN string
Audience string
UseRegionalSTS bool
}

type ServiceAccountCache interface {
Start()
Get(name, namespace string) (role, aud string)
Get(name, namespace string) (role, aud string, useRegionalSTS bool)
}

type serviceAccountCache struct {
mu sync.RWMutex // guards cache
cache map[string]*CacheResponse
store cache.Store
controller cache.Controller
clientset kubernetes.Interface
annotationPrefix string
defaultAudience string
mu sync.RWMutex // guards cache
cache map[string]*CacheResponse
store cache.Store
controller cache.Controller
clientset kubernetes.Interface
annotationPrefix string
defaultAudience string
defaultRegionalSts bool
}

func (c *serviceAccountCache) Get(name, namespace string) (role, aud string) {
func (c *serviceAccountCache) Get(name, namespace string) (role, aud string, useRegionalSTS bool) {
klog.V(5).Infof("Fetching sa %s/%s from cache", namespace, name)
resp := c.get(name, namespace)
if resp == nil {
return "", ""
return "", "", false
}
return resp.RoleARN, resp.Audience
return resp.RoleARN, resp.Audience, resp.UseRegionalSTS
}

func (c *serviceAccountCache) get(name, namespace string) *CacheResponse {
Expand All @@ -76,14 +80,23 @@ func (c *serviceAccountCache) pop(name, namespace string) {
}

func (c *serviceAccountCache) addSA(sa *v1.ServiceAccount) {
arn, ok := sa.Annotations[c.annotationPrefix+"/role-arn"]
arn, ok := sa.Annotations[c.annotationPrefix+"/"+pkg.RoleARNAnnotation]
resp := &CacheResponse{}
if ok {
resp.RoleARN = arn
if audience, ok := sa.Annotations[c.annotationPrefix+"/audience"]; ok {
resp.Audience = c.defaultAudience
if audience, ok := sa.Annotations[c.annotationPrefix+"/"+pkg.AudienceAnnotation]; ok {
resp.Audience = audience
} else {
resp.Audience = c.defaultAudience
}

resp.UseRegionalSTS = c.defaultRegionalSts
if disableRegionalStr, ok := sa.Annotations[c.annotationPrefix+"/"+pkg.UseRegionalSTSAnnotation]; ok {
disableRegional, err := strconv.ParseBool(disableRegionalStr)
if err != nil {
klog.V(4).Infof("Ignoring service account %s/%s invalid value for disable-regional-sts annotation", sa.Namespace, sa.Name)
} else {
resp.UseRegionalSTS = !disableRegional
}
}
}
klog.V(5).Infof("Adding sa %s/%s to cache", sa.Name, sa.Namespace)
Expand All @@ -96,11 +109,12 @@ func (c *serviceAccountCache) set(name, namespace string, resp *CacheResponse) {
c.cache[namespace+"/"+name] = resp
}

func New(defaultAudience, prefix string, clientset kubernetes.Interface) ServiceAccountCache {
func New(defaultAudience, prefix string, defaultRegionalSts bool, clientset kubernetes.Interface) ServiceAccountCache {
c := &serviceAccountCache{
cache: map[string]*CacheResponse{},
defaultAudience: defaultAudience,
annotationPrefix: prefix,
cache: map[string]*CacheResponse{},
defaultAudience: defaultAudience,
annotationPrefix: prefix,
defaultRegionalSts: defaultRegionalSts,
}

saListWatcher := cache.NewListWatchFromClient(
Expand Down
15 changes: 10 additions & 5 deletions pkg/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,33 @@ func TestSaCache(t *testing.T) {
testSA.Name = "default"
testSA.Namespace = "default"
roleArn := "arn:aws:iam::111122223333:role/s3-reader"
testSA.Annotations = map[string]string{"eks.amazonaws.com/role-arn": roleArn}
testSA.Annotations = map[string]string{
"eks.amazonaws.com/role-arn": roleArn,
"eks.amazonaws.com/sts-regional-endpoints": "true",
}

cache := &serviceAccountCache{
cache: map[string]*CacheResponse{},
defaultAudience: "sts.amazonaws.com",
annotationPrefix: "eks.amazonaws.com",
}

role, aud := cache.Get("default", "default")
role, aud, useRegionalSTS := cache.Get("default", "default")

if role != "" || aud != "" {
t.Errorf("Expected role and aud to be empty, got %s, %s", role, aud)
t.Errorf("Expected role and aud to be empty, got %s, %s, %t", role, aud, useRegionalSTS)
}

cache.addSA(testSA)

role, aud = cache.Get("default", "default")
role, aud, useRegionalSTS = cache.Get("default", "default")
if role != roleArn {
t.Errorf("Expected role to be %s, got %s", roleArn, role)
}
if aud != "sts.amazonaws.com" {
t.Errorf("Expected aud to be sts.amzonaws.com, got %s", aud)
}

if useRegionalSTS {
t.Error("Expected regional STS to be true, got false")
}
}
Loading

0 comments on commit e95bb68

Please sign in to comment.