From 12416e8b7429475aecf833d7a4dc01ec8854eff7 Mon Sep 17 00:00:00 2001 From: Carise F <14340+carise@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:56:20 -0700 Subject: [PATCH] Initial service account fetcher logic --- .../templates/_helpers.tpl | 2 +- go.mod | 35 ++++- go.sum | 123 ++++++++++++++++-- pkg/extensions/chainrole/chainrole.go | 109 +++++----------- pkg/extensions/chainrole/chainrole_test.go | 63 ++++----- pkg/extensions/chainrole/cmd.go | 37 +++++- .../chainrole/ekspodidentities/ekspia.go | 50 +++++++ .../chainrole/serviceaccount/k8s.go | 84 ++++++++++++ .../chainrole/serviceaccount/token.go | 49 +++++++ pkg/handlers/eks_credential_handler.go | 4 +- 10 files changed, 423 insertions(+), 133 deletions(-) create mode 100644 pkg/extensions/chainrole/ekspodidentities/ekspia.go create mode 100644 pkg/extensions/chainrole/serviceaccount/k8s.go create mode 100644 pkg/extensions/chainrole/serviceaccount/token.go diff --git a/charts/eks-pod-identity-agent/templates/_helpers.tpl b/charts/eks-pod-identity-agent/templates/_helpers.tpl index f61edc4..cbbce3a 100644 --- a/charts/eks-pod-identity-agent/templates/_helpers.tpl +++ b/charts/eks-pod-identity-agent/templates/_helpers.tpl @@ -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 }} {{/* diff --git a/go.mod b/go.mod index a188394..035a16b 100644 --- a/go.mod +++ b/go.mod @@ -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 ( @@ -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 ) diff --git a/go.sum b/go.sum index 1436429..d33a6a1 100644 --- a/go.sum +++ b/go.sum @@ -35,40 +35,82 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -77,8 +119,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -87,35 +129,94 @@ github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRM github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/extensions/chainrole/chainrole.go b/pkg/extensions/chainrole/chainrole.go index a52b1ec..80d4777 100644 --- a/pkg/extensions/chainrole/chainrole.go +++ b/pkg/extensions/chainrole/chainrole.go @@ -10,20 +10,21 @@ 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 ( @@ -31,13 +32,15 @@ type ( 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 } @@ -45,10 +48,9 @@ type ( 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") @@ -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) @@ -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 { @@ -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), @@ -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:: - 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) } diff --git a/pkg/extensions/chainrole/chainrole_test.go b/pkg/extensions/chainrole/chainrole_test.go index f121f63..c37bfc8 100644 --- a/pkg/extensions/chainrole/chainrole_test.go +++ b/pkg/extensions/chainrole/chainrole_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" - eksauthTypes "github.com/aws/aws-sdk-go-v2/service/eksauth/types" "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" @@ -209,7 +208,8 @@ func createTestToken(subject string) string { } type ( - mockRoleAssumer struct{} + mockRoleAssumer struct{} + mockSessionConfigRetriever struct{} ) func (m *mockRoleAssumer) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { @@ -226,8 +226,8 @@ func (m *mockRoleAssumer) AssumeRole(ctx context.Context, params *sts.AssumeRole }, nil } -func mockSessionConfiguration(ctx context.Context, awsCfg aws.Config, clusterName string, associationID string) (*sts.AssumeRoleInput, error) { - return &sts.AssumeRoleInput{}, nil +func (m *mockSessionConfigRetriever) GetSessionConfigMap(ctx context.Context, request *credentials.EksCredentialsRequest) (map[string]string, error) { + return map[string]string{}, nil } type responseMetadataTest string @@ -256,13 +256,14 @@ func TestCredentialRetriever_GetIamCredentials(t *testing.T) { logger.Initialize("DEBUG") tests := []struct { - name string - req *credentials.EksCredentialsRequest - want *credentials.EksCredentialsResponse - delegateErr error - wantErr error - namespaceFilter string - serviceaccountFilter string + name string + req *credentials.EksCredentialsRequest + want *credentials.EksCredentialsResponse + delegateErr error + expectedDelegateCalls int + wantErr error + namespaceFilter string + serviceaccountFilter string }{ { name: "Credentials request with no filter (chain logic skipped)", @@ -270,7 +271,8 @@ func TestCredentialRetriever_GetIamCredentials(t *testing.T) { ClusterName: "test-cluster-1", ServiceAccountToken: createTestToken("system:serviceaccount:test-namespace:test-service-account"), }, - want: testDataAssumeRoleForPodIdentityCreds, + want: testDataAssumeRoleForPodIdentityCreds, + expectedDelegateCalls: 1, }, { name: "Credentials request with namespace filter, no match (chain logic skipped)", @@ -279,7 +281,8 @@ func TestCredentialRetriever_GetIamCredentials(t *testing.T) { ClusterName: "test-cluster-1", ServiceAccountToken: createTestToken("system:serviceaccount:test-namespace:test-service-account"), }, - want: testDataAssumeRoleForPodIdentityCreds, + want: testDataAssumeRoleForPodIdentityCreds, + expectedDelegateCalls: 1, }, { name: "Credentials request with sa filter, no match (chain logic skipped)", @@ -288,7 +291,8 @@ func TestCredentialRetriever_GetIamCredentials(t *testing.T) { ClusterName: "test-cluster-1", ServiceAccountToken: createTestToken("system:serviceaccount:test-namespace:test-service-account"), }, - want: testDataAssumeRoleForPodIdentityCreds, + want: testDataAssumeRoleForPodIdentityCreds, + expectedDelegateCalls: 1, }, { name: "Credentials request with ns and sa filter, no match (chain logic skipped)", @@ -298,7 +302,8 @@ func TestCredentialRetriever_GetIamCredentials(t *testing.T) { ClusterName: "test-cluster-1", ServiceAccountToken: createTestToken("system:serviceaccount:test-namespace:test-service-account"), }, - want: testDataAssumeRoleForPodIdentityCreds, + want: testDataAssumeRoleForPodIdentityCreds, + expectedDelegateCalls: 1, }, { name: "Credentials request with namespace filter (chaining role)", @@ -328,30 +333,6 @@ func TestCredentialRetriever_GetIamCredentials(t *testing.T) { }, want: testDataAssumeRoleChainedCreds, }, - { - name: "Invalid Pod Identity Token", - req: &credentials.EksCredentialsRequest{}, - delegateErr: &eksauthTypes.InvalidTokenException{}, - wantErr: &eksauthTypes.InvalidTokenException{}, - }, - { - name: "Expired Pod Identity Token", - req: &credentials.EksCredentialsRequest{}, - delegateErr: &eksauthTypes.ExpiredTokenException{}, - wantErr: &eksauthTypes.ExpiredTokenException{}, - }, - { - name: "SA Unavailable Exception", - req: &credentials.EksCredentialsRequest{}, - delegateErr: &eksauthTypes.ServiceUnavailableException{}, - wantErr: &eksauthTypes.ServiceUnavailableException{}, - }, - { - name: "Access Denied Exception", - req: &credentials.EksCredentialsRequest{}, - delegateErr: &eksauthTypes.AccessDeniedException{}, - wantErr: &eksauthTypes.AccessDeniedException{}, - }, { name: "Invalid token", req: &credentials.EksCredentialsRequest{ @@ -379,13 +360,13 @@ func TestCredentialRetriever_GetIamCredentials(t *testing.T) { delegate: delegate, jwtParser: jwt.NewParser(), roleAssumer: &mockRoleAssumer{}, - getSessionConfig: mockSessionConfiguration, + sessionConfigRetriever: &mockSessionConfigRetriever{}, reNamespaceFilter: re(tt.namespaceFilter), reServiceAccountFilter: re(tt.serviceaccountFilter), } delegate.EXPECT().GetIamCredentials(gomock.Any(), gomock.Any()). - Return(testDataAssumeRoleForPodIdentityCreds, responseMetadataTest("test"), tt.delegateErr).Times(1) + Return(testDataAssumeRoleForPodIdentityCreds, responseMetadataTest("test"), tt.delegateErr).Times(tt.expectedDelegateCalls) got, _, err := c.GetIamCredentials(context.TODO(), tt.req) if tt.wantErr != nil { diff --git a/pkg/extensions/chainrole/cmd.go b/pkg/extensions/chainrole/cmd.go index c1244bf..7bc08a0 100644 --- a/pkg/extensions/chainrole/cmd.go +++ b/pkg/extensions/chainrole/cmd.go @@ -1,15 +1,48 @@ package chainrole import ( + "fmt" + "github.com/spf13/cobra" ) var ( - namespacePattern string - serviceAccountPattern string + namespacePattern string + serviceAccountPattern string + sessionConfigSourceVal sessionConfigSource ) func AddCMDFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&namespacePattern, "chainrole-namespace-pattern", "", "", "Namespace pattern to apply chain role functionality") cmd.Flags().StringVarP(&serviceAccountPattern, "chainrole-service-account-pattern", "", "", "Service account pattern to apply chain role functionality") + cmd.Flags().StringVarP(&serviceAccountPattern, "chainrole-session-config-source", "", "", "Service account pattern to apply chain role functionality") + cmd.Flags().Var(&sessionConfigSourceVal, "session-config-source", fmt.Sprintf(`Source from where to get session configurations, must be %q or %q`, eksPodIdentityAssociationTags, serviceAccountAnnotations)) +} + +type sessionConfigSource string + +const ( + eksPodIdentityAssociationTags = "eks-pod-identity-association-tags" + serviceAccountAnnotations = "service-account-annotations" +) + +// String is used both by fmt.Print and by Cobra in help text +func (e *sessionConfigSource) String() string { + return string(*e) +} + +// Set must have pointer receiver so it doesn't change the value of a copy +func (e *sessionConfigSource) Set(v string) error { + switch v { + case eksPodIdentityAssociationTags, serviceAccountAnnotations: + *e = sessionConfigSource(v) + return nil + default: + return fmt.Errorf(`must be one of %q or %q`, eksPodIdentityAssociationTags, serviceAccountAnnotations) + } +} + +// Type is only used in help text +func (e *sessionConfigSource) Type() string { + return "session-config-source" } diff --git a/pkg/extensions/chainrole/ekspodidentities/ekspia.go b/pkg/extensions/chainrole/ekspodidentities/ekspia.go new file mode 100644 index 0000000..404da93 --- /dev/null +++ b/pkg/extensions/chainrole/ekspodidentities/ekspia.go @@ -0,0 +1,50 @@ +package ekspodidentities + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "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" + "go.amzn.com/eks/eks-pod-identity-agent/internal/cloud/eksauth" + "go.amzn.com/eks/eks-pod-identity-agent/pkg/credentials" +) + +type podIdentityAssociationRetriever struct { + eksAuth eksauth.Iface +} + +func NewSessionConfigRetriever(eksAuth eksauth.Iface) *podIdentityAssociationRetriever { + return &podIdentityAssociationRetriever{eksAuth} +} + +func (r *podIdentityAssociationRetriever) GetSessionConfigMap(ctx context.Context, request *credentials.EksCredentialsRequest) (map[string]string, error) { + resp, metadata, err := r.eksAuth.GetIamCredentials(ctx, request) + if err != nil { + return nil, err + } + + // Assume eks pod identity credentials + podIdentityCfg, err := config.LoadDefaultConfig(context.TODO(), config.WithCredentialsProvider( + awsCreds.NewStaticCredentialsProvider(resp.AccessKeyId, resp.SecretAccessKey, resp.Token), + )) + if err != nil { + return nil, fmt.Errorf("error loading pod identity credentials: %w", err) + } + + clusterName, associationID := metadata.AssociationId(), request.ClusterName + + podIdentityAssociation, err := eks.NewFromConfig(podIdentityCfg).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) + } + + return podIdentityAssociation.Association.Tags, nil + +} diff --git a/pkg/extensions/chainrole/serviceaccount/k8s.go b/pkg/extensions/chainrole/serviceaccount/k8s.go new file mode 100644 index 0000000..6b10435 --- /dev/null +++ b/pkg/extensions/chainrole/serviceaccount/k8s.go @@ -0,0 +1,84 @@ +package serviceaccount + +import ( + "context" + "errors" + "strings" + "time" + + "go.amzn.com/eks/eks-pod-identity-agent/pkg/credentials" + authV1 "k8s.io/api/authentication/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" + k8s "k8s.io/client-go/kubernetes" + typedauthV1 "k8s.io/client-go/kubernetes/typed/authentication/v1" + listersV1 "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/rest" +) + +type serviceAccountRetriever struct { + lister listersV1.ServiceAccountLister + tokenReviewer typedauthV1.TokenReviewInterface +} + +func NewSessionConfigRetriever() *serviceAccountRetriever { + // Create a Kubernetes client + config, err := rest.InClusterConfig() + if err != nil { + panic(err) + } + clientset, err := k8s.NewForConfig(config) + if err != nil { + panic(err) + } + + return &serviceAccountRetriever{ + lister: informers.NewSharedInformerFactory(clientset, time.Hour).Core().V1().ServiceAccounts().Lister(), + tokenReviewer: clientset.AuthenticationV1().TokenReviews(), + } +} + +func (r *serviceAccountRetriever) GetSessionConfigMap(ctx context.Context, request *credentials.EksCredentialsRequest) (map[string]string, error) { + ns, saName, err := r.validateToken(ctx, request.ServiceAccountToken) + if err != nil { + return nil, err + } + + sa, err := r.lister.ServiceAccounts(ns).Get(saName) + if err != nil { + return nil, err + } + + return sa.Annotations, nil +} + +func (r *serviceAccountRetriever) validateToken(ctx context.Context, token string) (namespace, serviceaccount string, err error) { + review, err := r.tokenReviewer.Create(ctx, &authV1.TokenReview{ + Spec: authV1.TokenReviewSpec{ + Token: token, + }, + }, metaV1.CreateOptions{}) + if err != nil { + return "", "", err + } + + if !review.Status.Authenticated { + return "", "", errors.New("token is invalid") + } + + return ServiceAccountFromJWTSubject(review.Status.User.Username) +} + +func ServiceAccountFromJWTSubject(jwtSubject string) (namespace, serviceAccount string, err error) { + // subject is in the format: system:serviceaccount:: + if !strings.HasPrefix(jwtSubject, "system:serviceaccount:") { + return "", "", errors.New("JWT token claim subject doesn't start with 'system:serviceaccount:'") + } + + subjectParts := strings.Split(jwtSubject, ":") + if len(subjectParts) < 4 { + return "", "", errors.New("invalid JWT token claim subject") + } + + return subjectParts[2], subjectParts[3], nil +} diff --git a/pkg/extensions/chainrole/serviceaccount/token.go b/pkg/extensions/chainrole/serviceaccount/token.go new file mode 100644 index 0000000..6fb6cc2 --- /dev/null +++ b/pkg/extensions/chainrole/serviceaccount/token.go @@ -0,0 +1,49 @@ +package serviceaccount + +import ( + "fmt" + + "github.com/golang-jwt/jwt/v5" +) + +// https://github.com/kubernetes/kubernetes/blob/master/pkg/serviceaccount/claims.go +type KubernetesIOClaims struct { + Namespace string `json:"namespace"` + Pod *PodInfo `json:"pod"` + ServiceAccount ServiceAccountInfo `json:"serviceaccount"` + WarnAfter int64 `json:"warnafter"` +} + +type PodInfo struct { + Name string `json:"name"` + UID string `json:"uid"` +} + +type ServiceAccountInfo struct { + Name string `json:"name"` + UID string `json:"uid"` +} + +var jwtParser = jwt.NewParser() + +func ServiceAccountFromJWT(token string) (*KubernetesIOClaims, string, error) { + parsedToken, _, err := jwtParser.ParseUnverified(token, &jwt.RegisteredClaims{}) + if err != nil { + return nil, "", fmt.Errorf("error parsing JWT token: %w", err) + } + + subject, err := parsedToken.Claims.GetSubject() + if err != nil { + return nil, "", fmt.Errorf("error reading JWT token subject: %w", err) + } + + if mapClaims, ok := parsedToken.Claims.(jwt.MapClaims); ok { + if ik8s, ok := mapClaims["kubernetes.io"]; ok { + if k8sClaims, ok := ik8s.(KubernetesIOClaims); ok { + return &k8sClaims, subject, nil + } + } + } + + return nil, subject, err +} diff --git a/pkg/handlers/eks_credential_handler.go b/pkg/handlers/eks_credential_handler.go index bf78ead..410cec4 100644 --- a/pkg/handlers/eks_credential_handler.go +++ b/pkg/handlers/eks_credential_handler.go @@ -15,9 +15,9 @@ import ( "go.amzn.com/eks/eks-pod-identity-agent/internal/middleware/logger" "go.amzn.com/eks/eks-pod-identity-agent/internal/validation" "go.amzn.com/eks/eks-pod-identity-agent/pkg/credentials" - "go.amzn.com/eks/eks-pod-identity-agent/pkg/extensions/chainrole" - "go.amzn.com/eks/eks-pod-identity-agent/pkg/errors" + + "go.amzn.com/eks/eks-pod-identity-agent/pkg/extensions/chainrole" ) type EksCredentialHandler struct {