{"id":22725388,"url":"https://github.com/ovotech/iam-service-account-controller","last_synced_at":"2025-08-08T05:33:07.365Z","repository":{"id":50125194,"uuid":"371746148","full_name":"ovotech/iam-service-account-controller","owner":"ovotech","description":"Kubernetes controller that automatically manages AWS IAM roles for ServiceAccounts","archived":false,"fork":false,"pushed_at":"2023-10-11T21:10:16.000Z","size":156,"stargazers_count":8,"open_issues_count":2,"forks_count":2,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-06-19T05:56:45.622Z","etag":null,"topics":["aws","iam-role","kubernetes","kubernetes-controller"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ovotech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-28T15:35:35.000Z","updated_at":"2022-02-05T14:46:10.000Z","dependencies_parsed_at":"2024-06-19T05:27:14.976Z","dependency_job_id":"b85b3ab1-7ba0-497d-80da-aacac91e1572","html_url":"https://github.com/ovotech/iam-service-account-controller","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ovotech%2Fiam-service-account-controller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ovotech%2Fiam-service-account-controller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ovotech%2Fiam-service-account-controller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ovotech%2Fiam-service-account-controller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ovotech","download_url":"https://codeload.github.com/ovotech/iam-service-account-controller/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229089204,"owners_count":18018390,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","iam-role","kubernetes","kubernetes-controller"],"created_at":"2024-12-10T16:10:00.809Z","updated_at":"2024-12-10T16:10:01.501Z","avatar_url":"https://github.com/ovotech.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# iam-service-account-controller\n\nKubernetes controller that automatically manages AWS IAM roles for ServiceAccounts.\n\nThis is for EKS clusters configured for [IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html).\n\nBased on the Kubernetes [sample-controller](https://github.com/kubernetes/sample-controller).\n\n## Motivation\n\nWe want to allow users with access to a k8s namespace to manage AWS IAM roles that can be assumed by ServiceAccounts in that namespace.\n\nThis controller transparently synchronises IAM roles with ServiceAccounts with appropriate annotations. This way our users can manage IAM roles for their ServiceAccounts without requiring direct access to AWS.\n\nNote that we do not allow users to directly control their role's policies like this, for security reasons.\n\nWe are using this as part of our secret management solution.\n\n## What does this do?\n\nIf you create the following ServiceAccount (note the annotations):\n\n```yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  annotations:\n    security.kaluza.com/iam-role-managed: \"true\"\n    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/k8s-sa_bar_foo\n  name: foo\n  namespace: bar\n```\n\nthe controller will automatically create an IAM role in the same account with an AssumeRolePolicyDocument that allows the ServiceAccount to assume the role:\n\n```console\n$ aws iam get-role --role-name k8s-sa_bar_foo\n{\n    \"Role\": {\n        \"Path\": \"/\",\n        \"RoleName\": \"k8s-sa_bar_foo\",\n        \"RoleId\": \"ABCDEFGHIJK1234567890\",\n        \"Arn\": \"arn:aws:iam::123456789012:role/k8s-sa_bar_foo\",\n        \"CreateDate\": \"2021-05-28T15:19:49+00:00\",\n        \"AssumeRolePolicyDocument\": {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n            {\n                \"Effect\": \"Allow\",\n                \"Principal\": {\n                    \"Federated\": \"arn:aws:iam::1234567889012:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/14758F1AFD44C09B7992073CCF00B43D\"\n                },\n                \"Action\": \"sts:AssumeRoleWithWebIdentity\",\n                \"Condition\": {\n                    \"StringEquals\": {\n                        \"oidc.eks.eu-west-1.amazonaws.com/id/14758F1AFD44C09B7992073CCF00B43D:sub\": \"system:serviceaccount:bar:foo\"\n                }\n                }\n            }\n            ]\n        },\n        \"MaxSessionDuration\": 3600,\n        \"Tags\": [\n            {\n                \"Key\": \"role.k8s.aws/managed-by\",\n                \"Value\": \"iam-service-account-controller\"\n            },\n            {\n                \"Key\": \"serviceaccount.k8s.aws/stack\",\n                \"Value\": \"bar/foo\"\n            },\n            {\n                \"Key\": \"role.k8s.aws/cluster\",\n                \"Value\": \"cluster\"\n            }\n        ],\n        \"RoleLastUsed\": {}\n    }\n}\n```\n\n## Running locally\n\nTo run locally, ensure you have AWS creds with sufficient permissions in your environment (see permissions required in \"Quick setup\" section below) and:\n\n```console\n$ aws eks update-kubeconfig --name $CLUSTER_NAME\n\n$ OIDC_PROVIDER=$(aws eks describe-cluster --name $CLUSTER_NAME --query \"cluster.identity.oidc.issuer\" --output text | sed -e \"s/^https:\\/\\///\")\n\n$ go run . -kubeconfig=$HOME/.kube/config -oidc-provider=$OIDC_PROVIDER -token-path=\"\"\n```\n\nNote that when `-token-path` is empty the controller will use the default AWS search path for credentials instead of Web ID token authentication, which is what we want when we run locally.\n\n## Quick setup\n\nThese instructions are for trying out the controller in your cluster. In practice you'll want set this up in a more formal manner.\n\nWe assume your EKS cluster is set up for [IAM Roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html).\n\n### IAM role for the controller\n\nWe first need to create any IAM role for our controller to assume from the cluster:\n\n```console\n$ NAMESPACE=iam-service-account-controller\n\n$ EKS_CLUSTER_NAME=cluster_name\n\n$ ACCOUNT_ID=$(aws sts get-caller-identity | jq -r '.Account')\n\n$ OIDC_PROVIDER=$(aws eks describe-cluster --name $EKS_CLUSTER_NAME --query \"cluster.identity.oidc.issuer\" --output text | sed -e \"s/^https:\\/\\///\")\n\n$ cat \u003c\u003cEOF \u003e /tmp/trust.json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Federated\": \"arn:aws:iam::$OIDC_PROVIDER\"\n      },\n      \"Action\": \"sts:AssumeRoleWithWebIdentity\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"$OIDC_PROVIDER\": \"system:serviceaccount:$NAMESPACE:iam-service-account-controller\"\n        }\n      }\n    }\n  ]\n}\nEOF\n\n$ cat \u003c\u003cEOF \u003e /tmp/policy.json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"iam:CreateRole\",\n                \"iam:DeleteRole\",\n                \"iam:GetRole\",\n                \"iam:TagRole\"\n            ],\n            \"Resource\": \"arn:aws:iam::$ACCOUNT_ID:role/k8s-sa_*\"\n        }\n    ]\n}\nEOF\n\n$ aws iam create-role \\\n    --role-name \"iam-service-account-controller\" \\\n    --assume-role-policy-document file:///tmp/trust.json \\\n    --description \"IAM role for the iam-service-account k8s controller\"\n\n$ aws iam put-role-policy \\\n    --role-name \"iam-service-account-controller\" \\\n    --policy-name \"iam-service-account-controller-policy\" \\\n    --policy-document file:///tmp/policy.json\n```\n\n### Build and push image to repository\n\nIf you're reading this as an external party: we're not providing images. You'll want to build the image and push it to an image repository accessible to your cluster. Here we're assuming you're set up to push to a private AWS ECR repository that can be accessed by your cluster:\n\n```console\n$ AWS_REGION=eu-west-1\n\n$ REPO_NAME=iam-service-account-controller\n\n$ GIT_TAG=$(git describe --tags --abbrev=0)\n\n$ IMAGE_TAG=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPO_NAME:$GIT_TAG\n\n$ aws ecr create-repository --repository-name $REPO_NAME\n\n$ docker image build -t $IMAGE_TAG .\n\n$ docker push $IMAGE_TAG\n```\n\n### Deploy controller\n\nFinally, we can deploy the controller. Stick this in a YAML file and apply it:\n\n```yaml\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: iam-service-account-controller\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: iam-service-account-controller\n  name: iam-service-account-controller\n  namespace: iam-service-account-controller\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: iam-service-account-controller\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: iam-service-account-controller\n    spec:\n      containers:\n        - name: iam-service-account-controller\n          imagePullPolicy: Always\n          args:\n            # roles managed by this controller are prefixed with this string\n            - -role-prefix=k8s-sa\n            # cluster OIDC provider URL without the \"https://\"\n            - -oidc-provider=oidc.eks.eu-west-1.amazonaws.com/id/14758F1AFD44C09B7992073CCF00B43D\n            # path to the IAM web ID token for pod authentication to AWS\n            - -token-path=/var/run/secrets/eks.amazonaws.com/serviceaccount/token\n            # ARN of the role assumed by the controller\n            - -role-arn=arn:aws:iam::123456789012:role/iam-service-account-controller\n          volumeMounts:\n            - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount\n              name: aws-iam-token\n              readOnly: true\n          image: 123456789012.dkr.ecr.eu-west-1.amazonaws.com/iam-service-account-controller:0.0.0\n      serviceAccountName: iam-service-account-controller\n      volumes:\n        - name: aws-iam-token\n          projected:\n            defaultMode: 420\n            sources:\n              - serviceAccountToken:\n                  audience: sts.amazonaws.com\n                  expirationSeconds: 86400\n                  path: token\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  annotations:\n    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/iam-service-account-controller\n  name: iam-service-account-controller\n  namespace: iam-service-account-controller\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: iam-service-account-controller\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"serviceaccounts\"]\n    verbs: [\"get\", \"watch\", \"list\"]\n  - apiGroups: [\"\"]\n    resources: [\"events\"]\n    verbs: [\"create\", \"patch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: iam-service-account-controller\nsubjects:\n  - kind: ServiceAccount\n    name: iam-service-account-controller\n    namespace: iam-service-account-controller\nroleRef:\n  kind: ClusterRole\n  name: iam-service-account-controller\n  apiGroup: rbac.authorization.k8s.io\n```\n\n### Test it\n\nIf you try to create this:\n\n```yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  annotations:\n    security.kaluza.com/iam-role-managed: \"true\"\n    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/k8s-sa_default_test\n  name: test\n  namespace: default\n```\n\n\u003e Note that the `eks.amazonaws.com/role-arn` value must match: `(optional-prefix_)namespace_service-account-name` - see help for more details.\n\nyou should see:\n\n```console\n$ kubectl -n iam-service-account-controller logs -f iam-service-account-controller-8595966fb5-12345\nW0602 15:40:33.396159       1 client_config.go:615] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.\nI0602 15:40:33.422062       1 controller.go:53] Creating event broadcaster\nI0602 15:40:33.425272       1 controller.go:76] Setting up event handlers\nI0602 15:40:33.425302       1 controller.go:98] Starting ServiceAccount controller\nI0602 15:40:33.425307       1 controller.go:101] Waiting for informer caches to sync\nI0602 15:40:33.527738       1 controller.go:106] Starting workers\nI0602 15:40:33.527767       1 controller.go:112] Started workers\nI0602 15:44:06.394955       1 controller.go:185] Syncing default/test\nI0602 15:44:06.711849       1 controller.go:237] No IAM Role for 'default/test'; creating it\nI0602 15:44:06.844645       1 controller.go:170] Successfully synced 'default/test'\nI0602 15:44:06.844664       1 controller.go:185] Syncing default/test\nI0602 15:44:06.845115       1 event.go:291] \"Event occurred\" object=\"default/test\" kind=\"ServiceAccount\" apiVersion=\"v1\" type=\"Normal\" reason=\"Synced\" message=\"Successfully synced AWS IAM role\"\nI0602 15:44:06.941159       1 controller.go:170] Successfully synced 'default/test'\nI0602 15:44:06.941600       1 event.go:291] \"Event occurred\" object=\"default/test\" kind=\"ServiceAccount\" apiVersion=\"v1\" type=\"Normal\" reason=\"Synced\" message=\"Successfully synced AWS IAM role\"\n```\n\nEnd-users can check events to help them debug:\n\n```console\n$ kubectl -n default get events\nLAST SEEN   TYPE      REASON            OBJECT                MESSAGE\n46s         Normal    Synced            serviceaccount/test   Successfully synced AWS IAM role\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fovotech%2Fiam-service-account-controller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fovotech%2Fiam-service-account-controller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fovotech%2Fiam-service-account-controller/lists"}