├── .github ├── ISSUE_TEMPLATE │ ├── bug-report-client.yaml │ ├── bug-report-server.yaml │ └── feature-request.yaml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yaml └── workflows │ ├── create-release.yml │ └── deps.yml ├── .gitignore ├── .go-version ├── .goreleaser.yaml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── OWNERS ├── README.md ├── SECURITY_CONTACTS ├── cloudbuild.yaml ├── cmd └── aws-iam-authenticator │ ├── add.go │ ├── init.go │ ├── root.go │ ├── server.go │ ├── token.go │ ├── verify.go │ └── version.go ├── code-of-conduct.md ├── deploy ├── example-iamidentitymapping.yaml ├── example.yaml └── iamidentitymapping.yaml ├── docs ├── RELEASE.md ├── development.md └── sso_role_matcher.md ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── check-vendor.sh ├── dev │ ├── access-entries-username-prefix.template │ ├── access-entries.json │ ├── access-entries.template │ ├── allow_assume_role_policy.template │ ├── authenticator.yaml │ ├── authenticator_with_dynamicfile_mode.yaml │ ├── env.yaml │ ├── extract_ca_data.py │ ├── kubeconfig.yaml │ └── policies.template ├── e2e-dynamicfile.sh ├── e2e │ ├── README.md │ ├── apiserver-restart.yaml │ ├── aws.sh │ ├── deploy.yaml │ ├── kops-patch.yaml │ ├── kops.sh │ ├── kubeconfig-update.yaml │ ├── roles.yaml │ ├── run.sh │ └── util.sh ├── get-version.sh ├── lib │ └── dev-env.sh ├── setup-go.sh ├── start-dev-env-dynamicfile.sh ├── start-dev-env.sh ├── stop-dev-env.sh ├── tag-release.sh ├── test-integration.sh ├── test-unit.sh ├── tools.go ├── update-codegen.sh └── update-gomod.sh ├── pkg ├── arn │ ├── arn.go │ ├── arn_test.go │ ├── arnlike.go │ └── arnlike_test.go ├── config │ ├── certs │ │ ├── certs.go │ │ └── certs_test.go │ ├── config.go │ ├── config_test.go │ ├── constants.go │ ├── features.go │ ├── kubeconfig │ │ └── kubeconfig.go │ ├── mapper.go │ ├── mapper_test.go │ └── types.go ├── ec2provider │ ├── ec2provider.go │ └── ec2provider_test.go ├── errutil │ └── errors.go ├── filecache │ ├── converter.go │ ├── filecache.go │ └── filecache_test.go ├── fileutil │ ├── util.go │ └── util_test.go ├── httputil │ ├── client.go │ └── client_test.go ├── mapper │ ├── configmap │ │ ├── client │ │ │ ├── client.go │ │ │ └── client_test.go │ │ ├── configmap.go │ │ ├── configmap_test.go │ │ ├── mapper.go │ │ ├── yaml │ │ │ ├── aws-auth-crazy-case-keys.yaml │ │ │ ├── aws-auth-crazy-case-top-keys.yaml │ │ │ ├── aws-auth-lower-case-top-keys.yaml │ │ │ ├── aws-auth-missing-bar.yaml │ │ │ ├── aws-auth-open-source-case-keys.yaml │ │ │ ├── aws-auth-space-out-of-place.yaml │ │ │ └── aws-auth.yaml │ │ └── yaml_test.go │ ├── crd │ │ ├── apis │ │ │ └── iamauthenticator │ │ │ │ ├── register.go │ │ │ │ └── v1alpha1 │ │ │ │ ├── doc.go │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ └── zz_generated.deepcopy.go │ │ ├── controller │ │ │ ├── controller.go │ │ │ └── controller_test.go │ │ ├── generated │ │ │ ├── clientset │ │ │ │ └── versioned │ │ │ │ │ ├── clientset.go │ │ │ │ │ ├── fake │ │ │ │ │ ├── clientset_generated.go │ │ │ │ │ ├── doc.go │ │ │ │ │ └── register.go │ │ │ │ │ ├── scheme │ │ │ │ │ ├── doc.go │ │ │ │ │ └── register.go │ │ │ │ │ └── typed │ │ │ │ │ └── iamauthenticator │ │ │ │ │ └── v1alpha1 │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_iamauthenticator_client.go │ │ │ │ │ └── fake_iamidentitymapping.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── iamauthenticator_client.go │ │ │ │ │ └── iamidentitymapping.go │ │ │ ├── informers │ │ │ │ └── externalversions │ │ │ │ │ ├── factory.go │ │ │ │ │ ├── generic.go │ │ │ │ │ ├── iamauthenticator │ │ │ │ │ ├── interface.go │ │ │ │ │ └── v1alpha1 │ │ │ │ │ │ ├── iamidentitymapping.go │ │ │ │ │ │ └── interface.go │ │ │ │ │ └── internalinterfaces │ │ │ │ │ └── factory_interfaces.go │ │ │ └── listers │ │ │ │ └── iamauthenticator │ │ │ │ └── v1alpha1 │ │ │ │ ├── expansion_generated.go │ │ │ │ └── iamidentitymapping.go │ │ └── mapper.go │ ├── dynamicfile │ │ ├── dynamicfile.go │ │ ├── dynamicfile_test.go │ │ └── mapper.go │ ├── file │ │ ├── mapper.go │ │ └── mapper_test.go │ ├── mapper.go │ └── mapper_test.go ├── metrics │ └── metrics.go ├── server │ ├── server.go │ ├── server_test.go │ └── types.go ├── token │ ├── token.go │ └── token_test.go └── version.go ├── tests ├── e2e │ ├── apiserver_test.go │ ├── go.mod │ ├── go.sum │ ├── suite_test.go │ └── tests.go └── integration │ ├── go.mod │ ├── go.sum │ ├── server │ ├── main_test.go │ └── server_test.go │ └── testutils │ ├── certs.go │ ├── kubeconfig.go │ └── testserver.go └── version.txt /.github/ISSUE_TEMPLATE/bug-report-client.yaml: -------------------------------------------------------------------------------- 1 | name: Client Bug Report 2 | description: Report a bug encountered while using AWS IAM Authenticator client 3 | title: "[Bug]: " 4 | labels: ["kind/bug", "needs-triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please use this template while reporting a client bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened? 14 | placeholder: Tell us what you see! 15 | value: "A bug happened!" 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: what-did-you-expect 20 | attributes: 21 | label: What you expected to happen? 22 | placeholder: Tell us what you want to see! 23 | value: "A bug happened!" 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: anything-else 28 | attributes: 29 | label: Anything else we need to know? 30 | validations: 31 | required: false 32 | - type: dropdown 33 | id: installation 34 | attributes: 35 | label: Installation tooling 36 | description: How did you install aws-iam-authenticator? 37 | options: 38 | - homebrew 39 | - go install 40 | - other (please specify in description) 41 | validations: 42 | required: true 43 | - type: input 44 | id: version 45 | attributes: 46 | label: AWS IAM Authenticator client version 47 | description: What version of the aws-iam-authenticator are you running? 48 | placeholder: Output from `aws-iam-authenticator version` 49 | validations: 50 | required: true 51 | - type: textarea 52 | id: client 53 | attributes: 54 | label: Client information 55 | description: | 56 | examples: 57 | - **OS/arch**: Ubuntu 20.04 x86 or macOS 12.5.1 intel 58 | - **kubernetes client & version**: kubectl or kubelet 59 | value: | 60 | - OS/arch: 61 | - kubernetes client & version: 62 | render: Markdown 63 | validations: 64 | required: true 65 | - type: input 66 | id: k8s-version 67 | attributes: 68 | label: Kubernetes API Version 69 | description: What version of the Kubernetes API are you running? 70 | placeholder: Output from `kubectl version` 71 | validations: 72 | required: true 73 | - type: textarea 74 | id: kubeconfig 75 | attributes: 76 | label: kubeconfig user 77 | description: | 78 | Please copy and paste the kubeconfig JSON from `kubectl config view --minify -o jsonpath-as-json="{.users[0]}" `. 79 | This will be automatically formatted into JSON, so no need for backticks. 80 | If you wish to redact any fields such as cluster name, replace with "REDACTED" 81 | render: JSON 82 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report-server.yaml: -------------------------------------------------------------------------------- 1 | name: Server Bug Report 2 | description: Report a bug encountered while operating AWS IAM Authenticator Server 3 | title: "[Bug]: " 4 | labels: ["kind/bug", "needs-triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please use this template while reporting a server bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened? 14 | placeholder: Tell us what you see! 15 | value: "A bug happened!" 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: what-did-you-expect 20 | attributes: 21 | label: What you expected to happen? 22 | placeholder: Tell us what you want to see! 23 | value: "A bug happened!" 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: anything-else 28 | attributes: 29 | label: Anything else we need to know? 30 | validations: 31 | required: false 32 | - type: dropdown 33 | id: installation 34 | attributes: 35 | label: Installation tooling 36 | description: How did you install aws-iam-authenticator server? 37 | options: 38 | - kOps 39 | - eks-anywhere 40 | - other (please specify in description) 41 | validations: 42 | required: true 43 | - type: input 44 | id: version 45 | attributes: 46 | label: AWS IAM Authenticator server Version 47 | description: What version of the aws-iam-authenticator are you running? 48 | placeholder: Output from `aws-iam-authenticator version` 49 | validations: 50 | required: true 51 | - type: textarea 52 | id: client 53 | attributes: 54 | label: Client information 55 | description: | 56 | examples: 57 | - **OS/arch**: Ubuntu 20.04 x86 or macOS 12.5.1 intel 58 | - **kubernetes client & version**: kubectl or kubelet 59 | - **authenticator client & version**: aws-cli/2.8.12 or aws-ium-authenticator v0.6.0 60 | value: | 61 | - OS/arch: 62 | - kubernetes client & version: 63 | - authenticator client & version: 64 | render: Markdown 65 | validations: 66 | required: false 67 | - type: input 68 | id: k8s-version 69 | attributes: 70 | label: Kubernetes API Version 71 | description: What version of the Kubernetes API are you running? 72 | placeholder: Output from `kubectl version` 73 | validations: 74 | required: true 75 | - type: textarea 76 | id: authenticator-manifest 77 | attributes: 78 | label: aws-iam-authenticator YAML manifest 79 | description: Please copy and paste the install YAML. This will be automatically formatted into yaml, so no need for backticks. 80 | render: YAML 81 | - type: textarea 82 | id: kube-apiserver-manifest 83 | attributes: 84 | label: kube-apiserver YAML manifest 85 | description: Please copy and paste the kube-apiserver YAML. This will be automatically formatted into yaml, so no need for backticks. 86 | render: YAML 87 | - type: textarea 88 | id: authenticator-logs 89 | attributes: 90 | label: aws-iam-authenticator logs 91 | description: Please copy and paste the relevant logs. This will be automatically formatted into json, so no need for backticks. 92 | render: JSON 93 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Submit an enhancement or feature request to aws-iam-authenticator 3 | title: "[Feature request]: " 4 | labels: ["kind/feature", "needs-triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please use this template to submit a feature request 10 | - type: textarea 11 | id: what-you-want 12 | attributes: 13 | label: What would you like to be added? 14 | placeholder: Tell us what you would like to see! 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: why 19 | attributes: 20 | label: Why is this needed? 21 | placeholder: Tell us why you want to see this! 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: anything-else 26 | attributes: 27 | label: Anything else we need to know? 28 | validations: 29 | required: false 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | **What this PR does / why we need it**: 9 | 10 | **Which issue(s) this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close the issue(s) when PR gets merged)*: 11 | Fixes # 12 | 13 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | version: 2 16 | enable-beta-ecosystems: true 17 | updates: 18 | - package-ecosystem: gomod 19 | directories: 20 | - '**/*' 21 | allow: 22 | - dependency-type: 'all' 23 | schedule: 24 | interval: weekly 25 | day: 'monday' 26 | time: '06:00' 27 | timezone: 'US/Pacific' 28 | groups: 29 | aws-dependencies: 30 | patterns: 31 | - 'github.com/aws/*' 32 | k8s-dependencies: 33 | patterns: 34 | - 'k8s.io*' 35 | - 'sigs.k8s.io*' 36 | misc-dependencies: 37 | patterns: 38 | - '*' 39 | exclude-patterns: 40 | - 'github.com/aws/*' 41 | - 'k8s.io*' 42 | - 'sigs.k8s.io*' 43 | labels: 44 | - 'area/dependency' 45 | - 'ok-to-test' 46 | - package-ecosystem: 'github-actions' 47 | directory: '/' 48 | schedule: 49 | interval: weekly 50 | day: 'monday' 51 | time: '06:00' 52 | timezone: 'US/Pacific' 53 | groups: 54 | actions: 55 | patterns: 56 | - '*' 57 | labels: 58 | - 'area/dependency' 59 | - 'ok-to-test' 60 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | # Github Action to create a release with goreleaser 2 | name: Create Release 3 | 4 | on: 5 | push: 6 | branches: 7 | - 'master' 8 | paths: 9 | - version.txt 10 | 11 | jobs: 12 | release: 13 | if: ${{ github.repository == 'kubernetes-sigs/aws-iam-authenticator' }} 14 | runs-on: ubuntu-latest 15 | 16 | permissions: 17 | contents: write 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | ssh-key: "${{ secrets.RELEASE_KEY }}" 25 | 26 | - name: Tag release 27 | run: | 28 | /usr/bin/git config --global user.email actions@github.com 29 | /usr/bin/git config --global user.name 'GitHub Actions Release Tagger' 30 | hack/tag-release.sh 31 | 32 | - name: Set up Go 33 | uses: actions/setup-go@v5 34 | 35 | - name: Run GoReleaser 36 | uses: goreleaser/goreleaser-action@v6 37 | with: 38 | distribution: goreleaser 39 | version: '~> v2' 40 | args: release --clean 41 | env: 42 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 43 | -------------------------------------------------------------------------------- /.github/workflows/deps.yml: -------------------------------------------------------------------------------- 1 | name: "Dependency Review" 2 | 3 | on: [pull_request, workflow_dispatch] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | dependency-review: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Dependency review 16 | uses: actions/dependency-review-action@v4 17 | 18 | govulncheck: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - id: govulncheck 25 | uses: golang/govulncheck-action@v1 26 | with: 27 | go-version-file: go.mod 28 | 29 | # [Info] Shows version of go that is (was) used 30 | - run: go version 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | aws-iam-authenticator 2 | 3 | bin 4 | 5 | /dist 6 | /_output 7 | 8 | # For testing 9 | test-artifacts 10 | hack/e2e/e2e-test-artifacts 11 | 12 | # == kubernetes .gitignore == 13 | 14 | # OSX leaves these everywhere on SMB shares 15 | ._* 16 | 17 | # OSX trash 18 | .DS_Store 19 | 20 | # Eclipse files 21 | .classpath 22 | .project 23 | .settings/** 24 | 25 | # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA 26 | .idea/ 27 | *.iml 28 | 29 | # Vscode files 30 | .vscode 31 | 32 | # Emacs save files 33 | *~ 34 | \#*\# 35 | .\#* 36 | 37 | # Vim-related files 38 | [._]*.s[a-w][a-z] 39 | [._]s[a-w][a-z] 40 | *.un~ 41 | Session.vim 42 | .netrwhist 43 | 44 | # cscope-related files 45 | cscope.* 46 | 47 | # Mercurial files 48 | **/.hg 49 | **/.hg* 50 | 51 | # Vagrant 52 | .vagrant 53 | 54 | # User cluster configs 55 | .kubeconfig 56 | 57 | .tags* 58 | 59 | # make-related metadata 60 | /.make/ 61 | 62 | # Godeps workspace 63 | /Godeps/_workspace 64 | 65 | /bazel-* 66 | *.pyc 67 | 68 | # local dot files 69 | .envrc 70 | 71 | # coverage.out file 72 | coverage.out 73 | coverage.html 74 | 75 | # multi-module workspaces 76 | go.work* 77 | -------------------------------------------------------------------------------- /.go-version: -------------------------------------------------------------------------------- 1 | 1.24.2 2 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # Configuration for https://goreleaser.com/ 2 | project_name: authenticator 3 | 4 | builds: 5 | - id: aws-iam-authenticator 6 | binary: aws-iam-authenticator 7 | main: ./cmd/aws-iam-authenticator/ 8 | goos: 9 | - darwin 10 | - linux 11 | - windows 12 | goarch: 13 | - amd64 14 | - arm64 15 | - ppc64le 16 | - s390x 17 | ignore: 18 | - goos: windows 19 | goarch: arm64 20 | env: 21 | - CGO_ENABLED=0 22 | ldflags: 23 | - "-s -w -X sigs.k8s.io/aws-iam-authenticator/pkg.Version={{.Version}} -X sigs.k8s.io/aws-iam-authenticator/pkg.CommitID={{.Commit}} -buildid=''" 24 | 25 | snapshot: 26 | name_template: "git-{{.ShortCommit}}" 27 | 28 | archives: 29 | - id: bin 30 | format: binary 31 | 32 | release: 33 | github: 34 | owner: kubernetes-sigs 35 | name: aws-iam-authenticator 36 | name_template: "v{{.Version}}" 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Sign the CLA 2 | 3 | Before you can contribute, you will need to sign the [Contributor License Agreement](https://github.com/kubernetes/community/blob/master/CLA.md). 4 | 5 | ## Code of Conduct 6 | 7 | Please make sure to read and observe our [Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | ARG image=public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-nonroot:2024-10-01-1727740884.2023 15 | ARG golang_image=public.ecr.aws/docker/library/golang:1.24.2 16 | 17 | FROM --platform=$BUILDPLATFORM $golang_image AS builder 18 | WORKDIR /go/src/github.com/kubernetes-sigs/aws-iam-authenticator 19 | COPY . . 20 | RUN go version 21 | 22 | ARG TARGETOS TARGETARCH 23 | RUN GOPROXY=direct GOOS=$TARGETOS GOARCH=$TARGETARCH make bin 24 | RUN chown 65532 _output/bin/aws-iam-authenticator 25 | 26 | FROM --platform=$TARGETPLATFORM public.ecr.aws/eks-distro/kubernetes/go-runner:v0.18.0-eks-1-32-latest AS go-runner 27 | 28 | FROM --platform=$TARGETPLATFORM $image 29 | COPY --from=go-runner /go-runner /usr/local/bin/go-runner 30 | COPY --from=builder /go/src/github.com/kubernetes-sigs/aws-iam-authenticator/_output/bin/aws-iam-authenticator /aws-iam-authenticator 31 | ENTRYPOINT ["/aws-iam-authenticator"] 32 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - micahhausler 3 | - wongma7 4 | - jyotimahapatra 5 | - kmala 6 | - yue9944882 7 | 8 | reviewers: 9 | - micahhausler 10 | - wongma7 11 | - jyotimahapatra 12 | - kmala 13 | - yue9944882 14 | 15 | emeritus_approvers: 16 | - christopherhein 17 | - mattmoyer 18 | - mattlandis 19 | - jaypipes 20 | - nckturner 21 | - nnmin-aws 22 | -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact point for the Product Security Team to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | micahhausler 14 | wongma7 15 | jyotimahapatra 16 | nnmin-aws 17 | dims 18 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | options: 2 | substitution_option: ALLOW_LOOSE 3 | steps: 4 | - name: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20221214-1b4dd4d69a 5 | entrypoint: /buildx-entrypoint 6 | args: 7 | - build 8 | - --tag=gcr.io/$PROJECT_ID/aws-iam-authenticator:$_GIT_TAG 9 | - --tag=gcr.io/$PROJECT_ID/aws-iam-authenticator:latest 10 | - --platform=linux/amd64,linux/arm64 11 | - --output=type=registry 12 | - . 13 | substitutions: 14 | _GIT_TAG: '12345' 15 | _PULL_BASE_REF: 'master' 16 | timeout: 1600s 17 | -------------------------------------------------------------------------------- /cmd/aws-iam-authenticator/init.go: -------------------------------------------------------------------------------- 1 | //go:build !no_init 2 | 3 | /* 4 | Copyright 2017 by the contributors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package main 20 | 21 | import ( 22 | "fmt" 23 | "os" 24 | 25 | "sigs.k8s.io/aws-iam-authenticator/pkg" 26 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 27 | 28 | "github.com/sirupsen/logrus" 29 | "github.com/spf13/cobra" 30 | "github.com/spf13/viper" 31 | ) 32 | 33 | var initCmd = &cobra.Command{ 34 | Use: "init", 35 | Short: "Pre-generate certificate, private key, and kubeconfig files for the server.", 36 | Long: ``, 37 | Run: func(cmd *cobra.Command, args []string) { 38 | fmt.Printf("Authenticator Version: %q, %q\n", pkg.Version, pkg.CommitID) 39 | cfg, err := getConfig() 40 | if err != nil { 41 | fmt.Fprintf(os.Stderr, "could not get config: %v\n", err) 42 | os.Exit(1) 43 | } 44 | 45 | if featureGates.Enabled(config.ConfiguredInitDirectories) { 46 | if err := cfg.GenerateFiles(); err != nil { 47 | 48 | fmt.Fprintf(os.Stderr, "could not initialize: %v\n", err) 49 | os.Exit(1) 50 | } 51 | 52 | logrus.Infof("certificate generated at %s on kubernetes master node(s)", cfg.CertPath()) 53 | logrus.Infof("key generated at %s on kubernetes master node(s)", cfg.KeyPath()) 54 | logrus.Infof("kubeconfig generated at %s on kubernetes master node(s)", cfg.GenerateKubeconfigPath) 55 | } else { 56 | deprecatedCfg := cfg 57 | deprecatedCfg.GenerateKubeconfigPath = "aws-iam-authenticator.kubeconfig" 58 | deprecatedCfg.StateDir = "./" 59 | 60 | err = deprecatedCfg.GenerateFiles() 61 | if err != nil { 62 | fmt.Fprintf(os.Stderr, "could not initialize: %v\n", err) 63 | os.Exit(1) 64 | } 65 | 66 | logrus.Infof("copy %s to %s on kubernetes master node(s)", deprecatedCfg.CertPath(), cfg.CertPath()) 67 | logrus.Infof("copy %s to %s on kubernetes master node(s)", deprecatedCfg.KeyPath(), cfg.KeyPath()) 68 | logrus.Infof("copy %s to %s on kubernetes master node(s)", deprecatedCfg.GenerateKubeconfigPath, cfg.GenerateKubeconfigPath) 69 | } 70 | 71 | logrus.Infof("configure your apiserver with `--authentication-token-webhook-config-file=%s` to enable authentication with aws-iam-authenticator", cfg.GenerateKubeconfigPath) 72 | }, 73 | } 74 | 75 | func init() { 76 | initCmd.Flags().String( 77 | "hostname", 78 | "localhost", 79 | "Hostname that should be used for writing the self-signed certificates") 80 | viper.BindPFlag("server.hostname", initCmd.Flags().Lookup("hostname")) 81 | 82 | initCmd.Flags().String( 83 | "address", 84 | "127.0.0.1", 85 | "IP Address to bind the server to listen to. (should be a 127.0.0.1 or 0.0.0.0)") 86 | viper.BindPFlag("server.address", initCmd.Flags().Lookup("address")) 87 | 88 | rootCmd.AddCommand(initCmd) 89 | } 90 | -------------------------------------------------------------------------------- /cmd/aws-iam-authenticator/token.go: -------------------------------------------------------------------------------- 1 | //go:build !no_token 2 | 3 | /* 4 | Copyright 2017 by the contributors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package main 20 | 21 | import ( 22 | "fmt" 23 | "os" 24 | 25 | "sigs.k8s.io/aws-iam-authenticator/pkg/token" 26 | 27 | "github.com/spf13/cobra" 28 | "github.com/spf13/viper" 29 | ) 30 | 31 | var tokenCmd = &cobra.Command{ 32 | Use: "token", 33 | Short: "Authenticate using AWS IAM and get token for Kubernetes", 34 | Long: ``, 35 | Run: func(cmd *cobra.Command, args []string) { 36 | region := viper.GetString("region") 37 | roleARN := viper.GetString("role") 38 | externalID := viper.GetString("externalID") 39 | clusterID := viper.GetString("clusterID") 40 | tokenOnly := viper.GetBool("tokenOnly") 41 | forwardSessionName := viper.GetBool("forwardSessionName") 42 | sessionName := viper.GetString("sessionName") 43 | cache := viper.GetBool("cache") 44 | 45 | if clusterID == "" { 46 | fmt.Fprintf(os.Stderr, "Error: cluster ID not specified\n") 47 | cmd.Usage() 48 | os.Exit(1) 49 | } 50 | 51 | if forwardSessionName && sessionName != "" { 52 | fmt.Fprintf(os.Stderr, "Error: cannot specify both --forward-session-name and --session-name parameter\n") 53 | cmd.Usage() 54 | os.Exit(1) 55 | } 56 | 57 | var tok token.Token 58 | var out string 59 | var err error 60 | gen, err := token.NewGenerator(forwardSessionName, cache) 61 | if err != nil { 62 | fmt.Fprintf(os.Stderr, "could not get token: %v\n", err) 63 | os.Exit(1) 64 | } 65 | 66 | tok, err = gen.GetWithOptions(&token.GetTokenOptions{ 67 | ClusterID: clusterID, 68 | AssumeRoleARN: roleARN, 69 | AssumeRoleExternalID: externalID, 70 | SessionName: sessionName, 71 | Region: region, 72 | }) 73 | if err != nil { 74 | fmt.Fprintf(os.Stderr, "could not get token: %v\n", err) 75 | os.Exit(1) 76 | } 77 | if tokenOnly { 78 | out = tok.Token 79 | } else { 80 | out = gen.FormatJSON(tok) 81 | } 82 | fmt.Println(out) 83 | }, 84 | } 85 | 86 | func init() { 87 | rootCmd.AddCommand(tokenCmd) 88 | tokenCmd.Flags().String("region", "", "AWS region to use for assume role calls") 89 | tokenCmd.Flags().StringP("role", "r", "", "Assume an IAM Role ARN before signing this token") 90 | tokenCmd.Flags().StringP("external-id", "e", "", "External ID to pass when assuming the IAM Role") 91 | tokenCmd.Flags().StringP("session-name", "s", "", "Session name to pass when assuming the IAM Role") 92 | tokenCmd.Flags().Bool("token-only", false, "Return only the token for use with Bearer token based tools") 93 | tokenCmd.Flags().Bool("forward-session-name", 94 | false, 95 | "Enable mapping a federated sessions caller-specified-role-name attribute onto newly assumed sessions. NOTE: Only applicable when a new role is requested via --role") 96 | tokenCmd.Flags().Bool("cache", false, "Cache the credential on disk until it expires. Uses the aws profile specified by AWS_PROFILE or the default profile.") 97 | viper.BindPFlag("region", tokenCmd.Flags().Lookup("region")) 98 | viper.BindPFlag("role", tokenCmd.Flags().Lookup("role")) 99 | viper.BindPFlag("externalID", tokenCmd.Flags().Lookup("external-id")) 100 | viper.BindPFlag("tokenOnly", tokenCmd.Flags().Lookup("token-only")) 101 | viper.BindPFlag("forwardSessionName", tokenCmd.Flags().Lookup("forward-session-name")) 102 | viper.BindPFlag("sessionName", tokenCmd.Flags().Lookup("session-name")) 103 | viper.BindPFlag("cache", tokenCmd.Flags().Lookup("cache")) 104 | viper.BindEnv("role", "DEFAULT_ROLE") 105 | } 106 | -------------------------------------------------------------------------------- /cmd/aws-iam-authenticator/verify.go: -------------------------------------------------------------------------------- 1 | //go:build !no_verify 2 | 3 | /* 4 | Copyright 2017 by the contributors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package main 20 | 21 | import ( 22 | "encoding/json" 23 | "fmt" 24 | "os" 25 | 26 | "sigs.k8s.io/aws-iam-authenticator/pkg/token" 27 | 28 | "github.com/aws/aws-sdk-go/aws/ec2metadata" 29 | "github.com/aws/aws-sdk-go/aws/endpoints" 30 | "github.com/aws/aws-sdk-go/aws/session" 31 | "github.com/spf13/cobra" 32 | "github.com/spf13/viper" 33 | ) 34 | 35 | var verifyCmd = &cobra.Command{ 36 | Use: "verify", 37 | Short: "Verify a token for debugging purpose", 38 | Long: ``, 39 | Run: func(cmd *cobra.Command, args []string) { 40 | tok := viper.GetString("token") 41 | output := viper.GetString("output") 42 | clusterID := viper.GetString("clusterID") 43 | partition := viper.GetString("partition") 44 | 45 | if tok == "" { 46 | fmt.Fprintf(os.Stderr, "error: token not specified\n") 47 | cmd.Usage() 48 | os.Exit(1) 49 | } 50 | 51 | if clusterID == "" { 52 | fmt.Fprintf(os.Stderr, "error: cluster ID not specified\n") 53 | cmd.Usage() 54 | os.Exit(1) 55 | } 56 | 57 | sess := session.Must(session.NewSession()) 58 | ec2metadata := ec2metadata.New(sess) 59 | instanceRegion, err := ec2metadata.Region() 60 | if err != nil { 61 | fmt.Printf("[Warn] Region not found in instance metadata, err: %v", err) 62 | } 63 | 64 | id, err := token.NewVerifier(clusterID, partition, instanceRegion).Verify(tok) 65 | if err != nil { 66 | fmt.Fprintf(os.Stderr, "could not verify token: %v\n", err) 67 | os.Exit(1) 68 | } 69 | 70 | if output == "json" { 71 | value, err := json.MarshalIndent(id, "", " ") 72 | if err != nil { 73 | fmt.Fprintf(os.Stderr, "could not unmarshal token: %v\n", err) 74 | } 75 | fmt.Printf("%s\n", value) 76 | } else { 77 | fmt.Printf("%+v\n", id) 78 | } 79 | }, 80 | } 81 | 82 | func init() { 83 | rootCmd.AddCommand(verifyCmd) 84 | verifyCmd.Flags().StringP("token", "t", "", "Token to verify") 85 | verifyCmd.Flags().StringP("output", "o", "", "Output format. Only `json` is supported currently.") 86 | viper.BindPFlag("token", verifyCmd.Flags().Lookup("token")) 87 | viper.BindPFlag("output", verifyCmd.Flags().Lookup("output")) 88 | 89 | partitionKeys := []string{} 90 | for _, p := range endpoints.DefaultPartitions() { 91 | partitionKeys = append(partitionKeys, p.ID()) 92 | } 93 | 94 | verifyCmd.Flags().String("partition", 95 | endpoints.AwsPartitionID, 96 | fmt.Sprintf("The AWS partition. Must be one of: %v", partitionKeys)) 97 | viper.BindPFlag("partition", verifyCmd.Flags().Lookup("partition")) 98 | 99 | } 100 | -------------------------------------------------------------------------------- /cmd/aws-iam-authenticator/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | "gopkg.in/yaml.v2" 10 | "sigs.k8s.io/aws-iam-authenticator/pkg" 11 | ) 12 | 13 | var ( 14 | shortened = false 15 | date = "" 16 | output = "" 17 | versionCmd = &cobra.Command{ 18 | Use: "version", 19 | Short: "Version will output the current build information", 20 | Long: ``, 21 | Run: func(_ *cobra.Command, _ []string) { 22 | ver := struct { 23 | Version string `json:"Version,omitempty"` 24 | Commit string `json:"Commit,omitempty"` 25 | Date string `json:"Date,omitempty"` 26 | }{pkg.Version, pkg.CommitID, date} 27 | 28 | switch { 29 | case shortened: 30 | fmt.Println(pkg.Version) 31 | case output == "json": 32 | json.NewEncoder(os.Stdout).Encode(ver) 33 | case output == "yaml": 34 | yaml.NewEncoder(os.Stdout).Encode(ver) 35 | default: 36 | fmt.Fprintln(os.Stderr, "unknown version option") 37 | } 38 | return 39 | }, 40 | } 41 | ) 42 | 43 | func init() { 44 | versionCmd.Flags().BoolVarP(&shortened, "short", "s", false, "Print just the version number.") 45 | versionCmd.Flags().StringVarP(&output, "output", "o", "json", "Output format. One of 'yaml' or 'json'.") 46 | rootCmd.AddCommand(versionCmd) 47 | } 48 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /deploy/example-iamidentitymapping.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: iamauthenticator.k8s.aws/v1alpha1 3 | kind: IAMIdentityMapping 4 | metadata: 5 | name: kubernetes-admin 6 | spec: 7 | arn: arn:aws:iam::XXXXXXXXXXXX:user/KubernetesAdmin 8 | username: kubernetes-admin 9 | groups: 10 | - system:masters -------------------------------------------------------------------------------- /deploy/iamidentitymapping.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: iamidentitymappings.iamauthenticator.k8s.aws 6 | spec: 7 | group: iamauthenticator.k8s.aws 8 | scope: Cluster 9 | names: 10 | plural: iamidentitymappings 11 | singular: iamidentitymapping 12 | kind: IAMIdentityMapping 13 | categories: 14 | - all 15 | versions: 16 | - name: v1alpha1 17 | served: true 18 | storage: true 19 | schema: 20 | openAPIV3Schema: 21 | type: object 22 | properties: 23 | spec: 24 | type: object 25 | required: 26 | - arn 27 | - username 28 | properties: 29 | arn: 30 | type: string 31 | username: 32 | type: string 33 | groups: 34 | type: array 35 | items: 36 | type: string 37 | status: 38 | type: object 39 | properties: 40 | canonicalARN: 41 | type: string 42 | userID: 43 | type: string 44 | subresources: 45 | status: {} 46 | -------------------------------------------------------------------------------- /docs/RELEASE.md: -------------------------------------------------------------------------------- 1 | # AWS IAM Authenticator Release Process 2 | 3 | ## Choosing the release version and branch 4 | 5 | Using semantic versioning, pick a release number that makes sense by bumping the major, minor or patch release version. If its a major or minor release (backwards incompatible changes, and new features, respectively) then you will want to start this process with an alpha release first. Here are some examples: 6 | 7 | Bumping a minor version after releasing a new feature: 8 | ``` 9 | v1.4.5 -> v1.5.0-alpha.0 10 | ``` 11 | 12 | After testing and allowing some time for feedback on the alpha, releasing v1.5.0: 13 | ``` 14 | v1.4.5 -> v1.5.0 15 | ``` 16 | 17 | New patch release: 18 | ``` 19 | v1.5.3 -> v1.5.4 20 | ``` 21 | 22 | New major version release with two alpha releases: 23 | ``` 24 | v1.6.2 -> v2.0.0-alpha.0 25 | -> v2.0.0-alpha.1 26 | -> v2.0.0 27 | ``` 28 | 29 | You also might need to create a release branch, if it doesn't already exist, if this release requires backporting changes to an older major or minor version. For example, in the case that we are backporting a fix to the v0.5 release branch, and we have a v0.6 release branch, then we would do the following: 30 | 31 | 1. Create the release branch (named release-0.5) if it doesn't exist from the last v0.5.x tagged release (or check it out if it already exists). 32 | 2. Cherry-pick the necessary commits onto the release branch. 33 | 3. Follow the instructions below to create the release commit. 34 | 4. Create a pull request to merge your fork of the release branch into the upstream release branch (i.e. nckturner/aws-iam-authenticator/release-0.5 -> kubernetes-sigs/aws-iam-authenticator/release-0.5). 35 | 5. CI will handle the rest automatically. This includes: 36 | - creating and pushing the git tag into the upstream release branch 37 | - running Goreleaser on the release branch 38 | - creating the GitHub release 39 | - Populating the release with the changes 40 | - building and uploading the binaries to the release 41 | 42 | ## Creating the release commit 43 | 44 | Update the `version.txt` with your new semantic version. This must be a standalone commit which only updates the `version.txt` file. 45 | 46 | Also, bump the image version in `deploy/example.yaml` to the new version. 47 | 48 | Push (or cherry-pick) the changes to a branch on your fork, and create a PR against the kubernetes-sigs upstream repository. 49 | 50 | ## Check the release on GitHub 51 | 52 | Look at the release that was just published and validate that the release has the appropriate assets. The assets should include the following: 53 | 54 | ``` 55 | authenticator_0.6.26_checksums.txt 56 | aws-iam-authenticator_0.6.26_darwin_amd64 57 | aws-iam-authenticator_0.6.26_darwin_arm64 58 | aws-iam-authenticator_0.6.26_linux_amd64 59 | aws-iam-authenticator_0.6.26_linux_arm64 60 | aws-iam-authenticator_0.6.26_linux_ppc64le 61 | aws-iam-authenticator_0.6.26_linux_s390x 62 | aws-iam-authenticator_0.6.26_windows_amd64.exe 63 | Source code (zip) 64 | Source code (tar.gz) 65 | ``` 66 | 67 | ## Post Release 68 | 69 | In a new PR after the images are pushed to ECR, update the yaml in `deploy/example.yaml`: 70 | 71 | ``` 72 | image: 602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon/aws-iam-authenticator:v0.5.2 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # AWS IAM Authenticator Development 2 | 3 | A simple [KIND](https://kind.sigs.k8s.io/) based test environment can be created with locally built images. 4 | 5 | ## Create the environment 6 | Run `make start-dev ADMIN_ARN=arn:aws:iam::... AUTHENTICATOR_IMAGE=aws-iam-authenticator:v0.5.3_02a8...` to create a kind cluster, and run an aws-iam-authenticator server container to test. 7 | Client side changes can be tested too, the binary in `_output/bin` is used by the generated kubeconfig. 8 | 9 | ## Tear down the environment 10 | Run `make kill-dev` to tear down the environment complete and remove the generated config. 11 | -------------------------------------------------------------------------------- /docs/sso_role_matcher.md: -------------------------------------------------------------------------------- 1 | # SSO Role Matcher 2 | 3 | Maps configuration for an AWS SSO managed IAM Role to a Kubernetes username and groups. 4 | 5 | ## Feature state 6 | 7 | Alpha 8 | 9 | ## Use case 10 | 11 | Easy and robust configuration for AWS SSO managed roles, which currently have two main issues: 12 | 13 | Firstly - confusing configuration. To use an SSO role, a user needs to map the Role ARN of the SSO ROle, minus the path. 14 | 15 | For example: given a permission set `MyPermissionSet`, region `us-east-1` and account number `000000000000`; AWS SSO 16 | creates a role: `arn:aws:iam::000000000000:role/aws-reserved/sso.amazonaws.com/us-east-1/AWSReservedSSO_MyPermissionSet_1234567890abcde`. 17 | 18 | To match this role, a user would need to create a mapRoles entry like: 19 | ``` 20 | mapRoles: | 21 | - rolearn: arn:aws:iam::000000000000:role/AWSReservedSSO_MyPermissionSet_1234567890abcde 22 | username: ... 23 | groups: ... 24 | ``` 25 | 26 | Secondly - brittle configuration. If AWS SSO recreates IAM Roles, they receive a different random suffix and all the users of that 27 | role can no longer authenticate to Kubernetes. 28 | 29 | ## New UX 30 | 31 | Users can create a mapRoles entry that will automatically match roles created by AWS SSO without needing to be updated 32 | every time the roles are changed. 33 | 34 | Users will now create mapRoles entries like: 35 | ``` 36 | mapRoles: | 37 | - sso: 38 | permissionSetName: MyPermissionSet 39 | accountID: "000000000000" 40 | username: ... 41 | groups: ... 42 | ``` 43 | 44 | If the user is using the aws-us-govt or aws-cn partitions, they must specify the partition attribute in the `sso` structure. 45 | ``` 46 | mapRoles: | 47 | - sso: 48 | permissionSetName: MyPermissionSet 49 | accountID: "000000000000" 50 | partition: "aws-us-govt" 51 | username: ... 52 | groups: ... 53 | ``` 54 | 55 | ## Implementation 56 | 57 | config.RoleMapping will be extended with a nested structure containing the necessary information to construct a canonicalized 58 | Role Arn. The random suffix will not need to be specified and will instead be matched for the user by constructing the 59 | expected ARN and applying a wildcard to the end. 60 | 61 | Users are protected from non-AWS SSO created roles as the AWS API prevents roles being manually created with AWSReservedSSO 62 | at the beginning of their names. 63 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module sigs.k8s.io/aws-iam-authenticator 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.55.7 7 | github.com/aws/aws-sdk-go-v2 v1.36.3 8 | github.com/fsnotify/fsnotify v1.9.0 9 | github.com/gofrs/flock v0.12.1 10 | github.com/google/go-cmp v0.7.0 11 | github.com/manifoldco/promptui v0.9.0 12 | github.com/prometheus/client_golang v1.22.0 13 | github.com/sirupsen/logrus v1.9.3 14 | github.com/spf13/afero v1.14.0 15 | github.com/spf13/cobra v1.9.1 16 | github.com/spf13/viper v1.20.1 17 | golang.org/x/time v0.11.0 18 | gopkg.in/yaml.v2 v2.4.0 19 | k8s.io/api v0.33.1 20 | k8s.io/apimachinery v0.33.1 21 | k8s.io/client-go v0.33.1 22 | k8s.io/code-generator v0.33.1 23 | k8s.io/component-base v0.33.1 24 | k8s.io/sample-controller v0.33.1 25 | sigs.k8s.io/yaml v1.4.0 26 | ) 27 | 28 | require ( 29 | github.com/aws/smithy-go v1.22.3 // indirect 30 | github.com/beorn7/perks v1.0.1 // indirect 31 | github.com/blang/semver/v4 v4.0.0 // indirect 32 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 33 | github.com/chzyer/readline v1.5.1 // indirect 34 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 35 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 36 | github.com/fxamacker/cbor/v2 v2.8.0 // indirect 37 | github.com/go-logr/logr v1.4.2 // indirect 38 | github.com/go-openapi/jsonpointer v0.21.1 // indirect 39 | github.com/go-openapi/jsonreference v0.21.0 // indirect 40 | github.com/go-openapi/swag v0.23.1 // indirect 41 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 42 | github.com/gogo/protobuf v1.3.2 // indirect 43 | github.com/google/gnostic-models v0.6.9 // indirect 44 | github.com/google/uuid v1.6.0 // indirect 45 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 46 | github.com/jmespath/go-jmespath v0.4.0 // indirect 47 | github.com/josharian/intern v1.0.0 // indirect 48 | github.com/json-iterator/go v1.1.12 // indirect 49 | github.com/mailru/easyjson v0.9.0 // indirect 50 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 51 | github.com/modern-go/reflect2 v1.0.2 // indirect 52 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 53 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 54 | github.com/pkg/errors v0.9.1 // indirect 55 | github.com/prometheus/client_model v0.6.2 // indirect 56 | github.com/prometheus/common v0.64.0 // indirect 57 | github.com/prometheus/procfs v0.16.1 // indirect 58 | github.com/sagikazarmark/locafero v0.9.0 // indirect 59 | github.com/sourcegraph/conc v0.3.0 // indirect 60 | github.com/spf13/cast v1.8.0 // indirect 61 | github.com/spf13/pflag v1.0.6 // indirect 62 | github.com/subosito/gotenv v1.6.0 // indirect 63 | github.com/x448/float16 v0.8.4 // indirect 64 | go.opentelemetry.io/otel v1.35.0 // indirect 65 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 66 | go.uber.org/multierr v1.11.0 // indirect 67 | golang.org/x/mod v0.24.0 // indirect 68 | golang.org/x/net v0.40.0 // indirect 69 | golang.org/x/oauth2 v0.30.0 // indirect 70 | golang.org/x/sync v0.14.0 // indirect 71 | golang.org/x/sys v0.33.0 // indirect 72 | golang.org/x/term v0.32.0 // indirect 73 | golang.org/x/text v0.25.0 // indirect 74 | golang.org/x/tools v0.33.0 // indirect 75 | google.golang.org/protobuf v1.36.6 // indirect 76 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 77 | gopkg.in/inf.v0 v0.9.1 // indirect 78 | gopkg.in/yaml.v3 v3.0.1 // indirect 79 | k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect 80 | k8s.io/klog/v2 v2.130.1 // indirect 81 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 82 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 83 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 84 | sigs.k8s.io/randfill v1.0.0 // indirect 85 | sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect 86 | ) 87 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /hack/check-vendor.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export GO111MODULE=on 4 | rm -rf vendor 5 | go mod vendor 6 | VENDOR_DIFF_LINES=$(git diff --numstat) 7 | 8 | if [[ -n "$VENDOR_DIFF_LINES" ]]; then 9 | echo "Vendored code did not match was expected" 10 | git diff 11 | exit 1 12 | fi 13 | -------------------------------------------------------------------------------- /hack/dev/access-entries-username-prefix.template: -------------------------------------------------------------------------------- 1 | { 2 | "mapRoles": [ 3 | { 4 | "rolearn": "arn:aws:iam::{{AWS_ACCOUNT}}:role/{{USERNAME_TEST_ROLE}}", 5 | "username": "{{SessionName}}:masters", 6 | "groups": [ 7 | "system:masters" 8 | ], 9 | "userid": "{{USER_ID}}" 10 | } 11 | ] 12 | } 13 | 14 | -------------------------------------------------------------------------------- /hack/dev/access-entries.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/aws-iam-authenticator/2e738a422c71edad318376c1f878366c2258154b/hack/dev/access-entries.json -------------------------------------------------------------------------------- /hack/dev/access-entries.template: -------------------------------------------------------------------------------- 1 | { 2 | "mapRoles": [ 3 | { 4 | "rolearn": "arn:aws:iam::{{AWS_ACCOUNT}}:role/{{AWS_TEST_ROLE}}", 5 | "username": "kubernetes-admin", 6 | "groups": [ 7 | "system:masters" 8 | ], 9 | "userid": "{{USER_ID}}" 10 | } 11 | ] 12 | } 13 | 14 | -------------------------------------------------------------------------------- /hack/dev/allow_assume_role_policy.template: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": { 4 | "Effect": "Allow", 5 | "Action": "sts:AssumeRole", 6 | "Resource": "arn:aws:iam::{{AWS_ACCOUNT}}:role/*" 7 | } 8 | } -------------------------------------------------------------------------------- /hack/dev/authenticator.yaml: -------------------------------------------------------------------------------- 1 | clusterID: {{CLUSTER_NAME}} 2 | server: 3 | address: {{AUTHENTICATOR_IP}} 4 | port: {{AUTHENTICATOR_PORT}} 5 | stateDir: {{AUTHENTICATOR_STATE_DIR}} 6 | generateKubeconfig: {{AUTHENTICATOR_GENERATE_KUBECONFIG}} 7 | kubeconfig: {{AUTHENTICATOR_KUBECONFIG}} 8 | mapUsers: 9 | - userARN: {{ADMIN_ARN}} 10 | username: kubernetes-admin 11 | groups: 12 | - system:masters 13 | -------------------------------------------------------------------------------- /hack/dev/authenticator_with_dynamicfile_mode.yaml: -------------------------------------------------------------------------------- 1 | clusterID: {{CLUSTER_NAME}} 2 | server: 3 | address: {{AUTHENTICATOR_IP}} 4 | port: {{AUTHENTICATOR_PORT}} 5 | stateDir: {{AUTHENTICATOR_STATE_DIR}} 6 | generateKubeconfig: {{AUTHENTICATOR_GENERATE_KUBECONFIG}} 7 | kubeconfig: {{AUTHENTICATOR_KUBECONFIG}} 8 | backendmode: [ "MountedFile", "DynamicFile" ] 9 | dynamicfilepath: {{AUTHENTICATOR_DYNAMICFILE_PATH}} 10 | dynamicBackendModePath: {{BACKENDMODE_PATH}} 11 | reservedPrefixConfig: 12 | - backendmode: DynamicFile 13 | usernamePrefixReserveList: 14 | - "aws:" 15 | - "iam:" 16 | - "amazon:" 17 | - "system:" 18 | - "eks:" 19 | mapUsers: 20 | - userARN: {{ADMIN_ARN}} 21 | username: kubernetes-admin 22 | groups: 23 | - system:masters 24 | -------------------------------------------------------------------------------- /hack/dev/env.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | name: {{CLUSTER_NAME}} 4 | networking: 5 | apiServerAddress: "127.0.0.1" 6 | # By default the API server listens on a random open port. 7 | # This allows us to avoid having to determine the random port in order to 8 | # pass it to authenticator. 9 | apiServerPort: 6443 10 | nodes: 11 | - role: control-plane 12 | kubeadmConfigPatches: 13 | - | 14 | kind: ClusterConfiguration 15 | apiServer: 16 | extraArgs: 17 | authentication-token-webhook-config-file: {{APISERVER_AUTH_WEBHOOK_KUBECONFIG}} 18 | extraVolumes: 19 | - name: webhookconfig 20 | hostPath: {{APISERVER_CONFIG_DEST_DIR}} 21 | mountPath: {{APISERVER_CONFIG_DEST_DIR}} 22 | readOnly: true 23 | pathType: "Directory" 24 | extraMounts: 25 | # Directory where authenticator generates the apiserver's authentication 26 | # webhook kubeconfig. 27 | - hostPath: {{AUTHENTICATOR_EXPORT_HOST_DIR}} 28 | containerPath: {{APISERVER_CONFIG_DEST_DIR}} 29 | readOnly: true 30 | -------------------------------------------------------------------------------- /hack/dev/extract_ca_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import sys 5 | import yaml 6 | 7 | if __name__ == '__main__': 8 | parser = argparse.ArgumentParser(description='Extract CA data from kubeconfig') 9 | parser.add_argument('--kubeconfig', help='The location of the kubeconfig to extract from.') 10 | parser.add_argument('--cluster', help='The kubeconfig cluster entry to extract from.') 11 | args = parser.parse_args() 12 | if args.kubeconfig == None: 13 | print('--kubeconfig is required', file=sys.stderr) 14 | sys.exit(1) 15 | if args.cluster == None: 16 | print('--cluster is required', file=sys.stderr) 17 | sys.exit(1) 18 | kubeconfig = args.kubeconfig 19 | cluster = args.cluster 20 | with open(kubeconfig, 'r') as f: 21 | config = yaml.safe_load(f) 22 | clusters = config['clusters'] 23 | for c in clusters: 24 | if c['name'] == cluster: 25 | print(c['cluster']['certificate-authority-data']) 26 | break 27 | else: 28 | print(f"Cluster {cluster} was not found in {kubeconfig}.", file=sys.stderr) 29 | sys.exit(1) 30 | -------------------------------------------------------------------------------- /hack/dev/kubeconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | certificate-authority-data: {{CERTIFICATE_AUTHORITY_DATA}} 5 | server: {{APISERVER_URL}} 6 | name: kind 7 | current-context: kind 8 | kind: Config 9 | preferences: {} 10 | contexts: 11 | - context: 12 | cluster: kind 13 | user: kind-authenticator 14 | name: test-authenticator 15 | users: 16 | - name: kind-authenticator 17 | user: 18 | exec: 19 | apiVersion: client.authentication.k8s.io/v1beta1 20 | args: 21 | - token 22 | - -i 23 | - {{CLUSTER_NAME}} 24 | command: {{AUTHENTICATOR_BIN}} 25 | env: 26 | - name: AWS_STS_REGIONAL_ENDPOINTS 27 | value: regional 28 | - name: AWS_DEFAULT_REGION 29 | value: {{REGION}} 30 | interactiveMode: IfAvailable 31 | provideClusterInfo: false 32 | -------------------------------------------------------------------------------- /hack/dev/policies.template: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": { 7 | "AWS": "arn:aws:iam::{{AWS_ACCOUNT}}:root" 8 | }, 9 | "Action": "sts:AssumeRole" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /hack/e2e/README.md: -------------------------------------------------------------------------------- 1 | # AWS IAM Authenticator E2E Testing 2 | 3 | End-to-end testing verifies the functionality of the AWS IAM authenticator for Kubernetes. 4 | 5 | ## Prerequisites 6 | 7 | Have the following installed on your system: 8 | - go (1.16+) 9 | - jq 10 | - awscli 11 | - Configure your AWS credentials, as well. 12 | - docker 13 | 14 | ## Operation 15 | 16 | From the base directory, run `./hack/e2e/run.sh`. Alternatively, run `make test-` or `GINKGO_FOCUS="[]" ./hack/e2e/run.sh` to run a specific subset of tests. 17 | 18 | You can change the behavior of the tests by setting certain environment variables beforehand. Most of these can stay unchanged, but some should be noted: 19 | - `REGION`, `ZONES`: AWS region that the tests should be run on. 20 | - `KOPS_STATE_FILE`: An S3 bucket that you have access to. **Change this to a bucket you own!** 21 | - `K8S_VERSION`: Kuberenetes version. Don't change this off `1.22.10`; you might end up with a bunch of build errors otherwise. 22 | - `TEST_ID`: Normally a random number, but can be set for a more controlled environment. 23 | - `CLEAN`: Set to false if you don't want the cluster to be torn down after the tests are done running. Useful if you want to inspect the cluster state after setup. 24 | 25 | :warning: Note that the tests may get stuck when patching the cluster file. To fix this issue, move or delete `~/.kube/config`. 26 | 27 | ## Tests 28 | 29 | ### Configuration tests 30 | 31 | These tests verify the basic functionality of the authenticator (specifically, that the ConfigMap applied to the cluster correctly assigns the identity based on the IAM role/user). 32 | 33 | Run these tests by running `./hack/e2e/run.sh`. 34 | 35 | Future work should be done to verify that EKS-style ConfigMaps and CRDs are also applied correctly. 36 | 37 | ### Apiserver tests 38 | 39 | When the authenticator is first installed on the cluster, the API server must be restarted with a modified manifest. This auxiliary test ensures that after a manifest modification, the API server restarts correctly. 40 | 41 | Run this test by running `GINKGO_FOCUS="\[apiserver\]" GINKGO_SKIP=".^" GINKGO_NODES=1 ./hack/e2e/run.sh`. 42 | -------------------------------------------------------------------------------- /hack/e2e/apiserver-restart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | namespace: kube-system 5 | name: apiserver-restarter 6 | spec: 7 | template: 8 | spec: 9 | hostNetwork: true 10 | nodeSelector: 11 | node-role.kubernetes.io/master: "" 12 | tolerations: 13 | - effect: NoSchedule 14 | key: node-role.kubernetes.io/master 15 | 16 | volumes: 17 | - name: manifest 18 | hostPath: 19 | path: /etc/kubernetes/manifests/kube-apiserver.manifest 20 | type: File 21 | 22 | containers: 23 | - name: apiserver-updater 24 | image: busybox 25 | command: ["sh", "-c", "echo '-' >> /etc/kubernetes/manifests/kube-apiserver.manifest && sleep 30 && echo \"$(sed '$d' /etc/kubernetes/manifests/kube-apiserver.manifest)\" > /etc/kubernetes/manifests/kube-apiserver.manifest"] 26 | 27 | securityContext: 28 | privileged: true 29 | runAsUser: 0 30 | runAsGroup: 0 31 | 32 | volumeMounts: 33 | - name: manifest 34 | mountPath: /etc/kubernetes/manifests/kube-apiserver.manifest 35 | 36 | restartPolicy: OnFailure 37 | -------------------------------------------------------------------------------- /hack/e2e/aws.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -uo pipefail 4 | 5 | BASE_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")") 6 | source "${BASE_DIR}"/util.sh 7 | 8 | function create_role() { 9 | ROLE_NAME=${1} 10 | ROLE_DESCRIPTION=${2} 11 | AWS_ACCOUNT_ID=${3} 12 | REGION=${4} 13 | 14 | set +e 15 | ROLE_INFO=$(aws iam get-role --region "${REGION}" --role-name="$ROLE_NAME") 16 | if [[ $? != 0 ]]; then 17 | set -e 18 | loudecho "Creating ${ROLE_NAME} role" >&2 19 | 20 | ## define a role trust policy that opens the role to users in your account (limited by IAM policy) 21 | POLICY=$(echo -n '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"arn:aws:iam::'; echo -n "$AWS_ACCOUNT_ID"; echo -n ':root"},"Action":"sts:AssumeRole"}]}') 22 | 23 | ## create a role named KubernetesAdmin (will print the new role's ARN) 24 | ROLE_ARN=$(aws iam create-role \ 25 | --region "${REGION}" \ 26 | --role-name "$ROLE_NAME" \ 27 | --description "$ROLE_DESCRIPTION" \ 28 | --assume-role-policy-document "$POLICY" \ 29 | --output text \ 30 | --query 'Role.Arn') 31 | else 32 | set -e 33 | loudecho "${ROLE_NAME} role already exists" >&2 34 | 35 | ROLE_ARN=$(echo "${ROLE_INFO}" | jq -r '.Role.Arn') 36 | fi 37 | 38 | echo "${ROLE_ARN}" 39 | } 40 | -------------------------------------------------------------------------------- /hack/e2e/kops-patch.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | authentication: 3 | aws: {} 4 | -------------------------------------------------------------------------------- /hack/e2e/kubeconfig-update.yaml: -------------------------------------------------------------------------------- 1 | - name: 2 | user: 3 | exec: 4 | apiVersion: client.authentication.k8s.io/v1beta1 5 | command: 6 | args: 7 | - "token" 8 | - "-i" 9 | - 10 | - "-r" 11 | - 12 | -------------------------------------------------------------------------------- /hack/e2e/roles.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | namespace: kube-system 5 | name: pods-role 6 | rules: 7 | - apiGroups: [""] 8 | resources: ["pods"] 9 | verbs: ["*"] 10 | --- 11 | apiVersion: rbac.authorization.k8s.io/v1 12 | kind: RoleBinding 13 | metadata: 14 | namespace: kube-system 15 | name: pods-binding 16 | subjects: 17 | - kind: Group 18 | name: pods-group 19 | apiGroup: rbac.authorization.k8s.io 20 | roleRef: 21 | kind: Role 22 | name: pods-role 23 | apiGroup: rbac.authorization.k8s.io 24 | --- 25 | apiVersion: rbac.authorization.k8s.io/v1 26 | kind: ClusterRole 27 | metadata: 28 | name: nodes-role 29 | rules: 30 | - apiGroups: [""] 31 | resources: ["nodes"] 32 | verbs: ["*"] 33 | --- 34 | apiVersion: rbac.authorization.k8s.io/v1 35 | kind: ClusterRoleBinding 36 | metadata: 37 | name: nodes-binding 38 | subjects: 39 | - kind: Group 40 | name: nodes-group 41 | apiGroup: rbac.authorization.k8s.io 42 | roleRef: 43 | kind: ClusterRole 44 | name: nodes-role 45 | apiGroup: rbac.authorization.k8s.io 46 | -------------------------------------------------------------------------------- /hack/e2e/util.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -uo pipefail 4 | 5 | function loudecho() { 6 | echo "###" 7 | echo "## ${1}" 8 | echo "#" 9 | } 10 | -------------------------------------------------------------------------------- /hack/get-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Copyright 2022 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | VERSION=$(cat version.txt) 18 | 19 | printerr() { echo "$@" 1>&2; } 20 | 21 | if [[ ! "${VERSION}" =~ ^([0-9]+[.][0-9]+)[.]([0-9]+)(-(alpha|beta)[.]([0-9]+))?$ ]]; then 22 | printerr "Version ${VERSION} must be 'X.Y.Z', 'X.Y.Z-alpha.N', or 'X.Y.Z-beta.N'" 23 | exit 1 24 | fi 25 | 26 | echo "v${VERSION}" 27 | -------------------------------------------------------------------------------- /hack/setup-go.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 The Kubernetes Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # script to setup go version as needed 17 | # MUST BE RUN FROM THE REPO ROOT DIRECTORY 18 | 19 | # read go-version file unless GO_VERSION is set 20 | GO_VERSION="${GO_VERSION:-"$(cat .go-version)"}" 21 | GO_IMAGE=public.ecr.aws/docker/library/golang:$GO_VERSION 22 | 23 | # gotoolchain 24 | # https://go.dev/doc/toolchain 25 | export GOSUMDB="sum.golang.org" 26 | export GOTOOLCHAIN=go${GO_VERSION} 27 | 28 | # force go modules 29 | export GO111MODULE=on 30 | 31 | echo $GO_IMAGE 32 | -------------------------------------------------------------------------------- /hack/start-dev-env-dynamicfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o pipefail 19 | set -o nounset 20 | 21 | # 22 | # An easy way to quickly test local changes. 23 | # 24 | # The approach taken was to start the aws-iam-authenticator as 25 | # a separate docker container first, with a fixed IP and in the 26 | # kind node network. Then, generate the webhook 27 | # config with that IP and start the kind cluster. 28 | # 29 | # Alternative approach: we could run the authenticator pod in 30 | # the kind cluster. This might be a future improvement: 31 | # pregenerate all certificates and config, start the cluster, 32 | # load the target authenticator image, run authenticator as a 33 | # host network pod (same as API server), and all communication 34 | # between them is over localhost and fixed port. 35 | 36 | REPO_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )" 37 | ADMIN_ARN=$(aws sts get-caller-identity --query Arn --output text) 38 | REPO_NAME=${REPO_NAME:-aws-iam-authenticator} 39 | 40 | source "${REPO_ROOT}/hack/lib/dev-env.sh" 41 | 42 | make image 43 | LOCAL_IMAGE_TAG=$(docker images | awk '{print $2}' | awk 'NR==2') 44 | AUTHENTICATOR_IMAGE="${REPO_NAME}":"${LOCAL_IMAGE_TAG}" 45 | 46 | echo ${ADMIN_ARN} ${AUTHENTICATOR_IMAGE} 47 | 48 | install_kind 49 | create_network 50 | write_authenticator_with_dynamicfile_mode_config 51 | start_authenticator_with_dynamicfile 52 | sleep 5 53 | echo "Authenticator running at $AUTHENTICATOR_IP" 54 | replace_authenticator_ip 55 | write_kind_config 56 | create_kind_cluster 57 | certificate_authority_data="$(extract_certificate_authority_data)" 58 | write_kubectl_kubeconfig 59 | -------------------------------------------------------------------------------- /hack/start-dev-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o pipefail 19 | set -o nounset 20 | 21 | # 22 | # An easy way to quickly test local changes. 23 | # 24 | # The approach taken was to start the aws-iam-authenticator as 25 | # a separate docker container first, with a fixed IP and in the 26 | # kind node network. Then, generate the webhook 27 | # config with that IP and start the kind cluster. 28 | # 29 | # Alternative approach: we could run the authenticator pod in 30 | # the kind cluster. This might be a future improvement: 31 | # pregenerate all certificates and config, start the cluster, 32 | # load the target authenticator image, run authenticator as a 33 | # host network pod (same as API server), and all communication 34 | # between them is over localhost and fixed port. 35 | 36 | REPO_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )" 37 | 38 | if [[ "${AUTHENTICATOR_IMAGE:-}" = "" ]]; then 39 | echo "Error: \$AUTHENTICATOR_IMAGE is unset." 40 | exit 1 41 | elif [[ "${ADMIN_ARN:-}" = "" ]]; then 42 | echo "Error: \$ADMIN_ARN is unset." 43 | exit 1 44 | fi 45 | 46 | source "${REPO_ROOT}/hack/lib/dev-env.sh" 47 | 48 | install_kind 49 | create_network 50 | write_authenticator_config 51 | start_authenticator 52 | sleep 5 53 | echo "Authenticator running at $AUTHENTICATOR_IP" 54 | replace_authenticator_ip 55 | write_kind_config 56 | create_kind_cluster 57 | certificate_authority_data="$(extract_certificate_authority_data)" 58 | write_kubectl_kubeconfig 59 | 60 | echo "." 61 | echo "Test authenticator with:" 62 | echo "kubectl --kubeconfig=\"${kubectl_kubeconfig}\" --context=\"test-authenticator\"" 63 | echo "." 64 | -------------------------------------------------------------------------------- /hack/stop-dev-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o pipefail 19 | set -o nounset 20 | 21 | # 22 | # An easy way to quickly test local changes. 23 | # 24 | # The approach taken was to start the aws-iam-authenticator as 25 | # a separate docker container first, with a fixed IP and in the 26 | # kind node network. Then, generate the webhook 27 | # config with that IP and start the kind cluster. 28 | # 29 | # Alternative approach: we could run the authenticator pod in 30 | # the kind cluster. This might be a future improvement: 31 | # pregenerate all certificates and config, start the cluster, 32 | # load the target authenticator image, run authenticator as a 33 | # host network pod (same as API server), and all communication 34 | # between them is over localhost and fixed port. 35 | 36 | REPO_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )" 37 | 38 | source "${REPO_ROOT}/hack/lib/dev-env.sh" 39 | 40 | # Tear down kind cluster 41 | delete_kind_cluster 42 | 43 | # Kill authenticator container 44 | kill_authenticator 45 | 46 | sleep 5 47 | 48 | # Tear down network 49 | delete_network 50 | -------------------------------------------------------------------------------- /hack/tag-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Copyright 2022 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | VERSION=$(cat version.txt) 18 | 19 | if [[ ! "${VERSION}" =~ ^([0-9]+[.][0-9]+)[.]([0-9]+)(-(alpha|beta)[.]([0-9]+))?$ ]]; then 20 | echo "Version ${VERSION} must be 'X.Y.Z', 'X.Y.Z-alpha.N', or 'X.Y.Z-beta.N'" 21 | exit 1 22 | fi 23 | 24 | if [ "$(git tag -l "v${VERSION}")" ]; then 25 | echo "Tag v${VERSION} already exists" 26 | exit 0 27 | fi 28 | 29 | git tag -a -m "Release ${VERSION}" "v${VERSION}" 30 | git push origin "v${VERSION}" 31 | -------------------------------------------------------------------------------- /hack/test-integration.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2016 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o pipefail 19 | set -o nounset 20 | 21 | command -v aws || { echo "Command 'aws' not found" && exit 1; } 22 | 23 | function account_from_default_credentials() { 24 | sts_account=$(aws sts get-caller-identity --query "Account" --output text) 25 | echo $sts_account 26 | } 27 | 28 | function role_arn_from_default_credentials() { 29 | sts_arn=$(aws sts get-caller-identity --query "Arn" --output text) 30 | tmp=${sts_arn#*/} 31 | role_name=${tmp%/*} 32 | tmp=${sts_arn%:*} 33 | account_id=${tmp##*:} 34 | echo "arn:aws:iam::${account_id}:role/${role_name}" 35 | } 36 | 37 | function write-role-policy() { 38 | local account_id=$1 39 | local file_name=$2 40 | 41 | cat > ${file_name} < /dev/null && pwd)" 62 | TEST_ARTIFACTS="${TEST_ARTIFACTS:-"${REPO_ROOT}/test-artifacts"}" 63 | TEST_ROLE_ARN="${TEST_ROLE_ARN:-$(role_arn_from_default_credentials)}" 64 | 65 | function cleanup { 66 | if [[ "${CREATE_TEST_ROLE}" = "true" ]]; then 67 | echo "Cleaning up test role ${GENERATED_TEST_ROLE_NAME}" 68 | aws iam delete-role --role-name "${GENERATED_TEST_ROLE_NAME}" || echo "Failed to clean up test role ${GENERATED_TEST_ROLE_NAME}" 69 | fi 70 | } 71 | trap cleanup EXIT 72 | 73 | if [[ "${CREATE_TEST_ROLE}" = "true" ]]; then 74 | echo "Creating test role ${GENERATED_TEST_ROLE_NAME}" 75 | write-role-policy "$(account_from_default_credentials)" ${GENERATED_TEST_ROLE_POLICY_FILE} 76 | create_role_output=$(aws iam create-role --role-name "${GENERATED_TEST_ROLE_NAME}" --assume-role-policy-document "file://${GENERATED_TEST_ROLE_POLICY_FILE}") 77 | rm ${GENERATED_TEST_ROLE_POLICY_FILE} 78 | TEST_ROLE_ARN="$(echo ${create_role_output} | jq -r '.Role.Arn')" 79 | fi 80 | 81 | source hack/setup-go.sh 82 | 83 | go version 84 | 85 | make clean 86 | make bin 87 | 88 | if [[ -d ${TEST_ARTIFACTS}/k8s.io/kubernetes ]]; then 89 | rm -rf ${TEST_ARTIFACTS}/k8s.io/kubernetes 90 | fi 91 | 92 | mkdir -p ${TEST_ARTIFACTS}/k8s.io/kubernetes 93 | git clone --branch ${KUBERNETES_TAG} --depth 1 https://github.com/kubernetes/kubernetes.git ${TEST_ARTIFACTS}/k8s.io/kubernetes --depth 1 94 | 95 | pushd ${TEST_ARTIFACTS}/k8s.io/kubernetes 96 | make generated_files 97 | ./hack/install-etcd.sh 98 | export PATH="${TEST_ARTIFACTS}/k8s.io/kubernetes/third_party/etcd:${PATH}" 99 | popd 100 | 101 | pushd ${REPO_ROOT}/tests/integration 102 | export AWS_REGION=${AWS_REGION:-us-west-2} 103 | go test -v ./server -test-artifacts-dir="${TEST_ARTIFACTS}" -authenticator-binary-path="${REPO_ROOT}/_output/bin/aws-iam-authenticator" -role-arn="${TEST_ROLE_ARN}" 104 | popd 105 | -------------------------------------------------------------------------------- /hack/test-unit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2016 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o pipefail 19 | set -o nounset 20 | 21 | # cd to the repo root and setup go 22 | REPO_ROOT="$(cd "$( dirname "${BASH_SOURCE[0]}" )"/.. &> /dev/null && pwd)" 23 | 24 | source hack/setup-go.sh 25 | 26 | pushd ${REPO_ROOT} 27 | 28 | go version 29 | go test -v -coverprofile=coverage.out -race sigs.k8s.io/aws-iam-authenticator/pkg/... 30 | go tool cover -html=coverage.out -o coverage.html 31 | popd 32 | -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | /* 5 | Copyright 2019 The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // This package imports things required by build scripts, to force `go mod` to see them as dependencies 21 | package tools 22 | 23 | import _ "k8s.io/code-generator" 24 | -------------------------------------------------------------------------------- /hack/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. 22 | CODE_GEN_VERSION=$(go mod edit -print |grep 'k8s.io/code-generator' | cut -f2 -d' ') 23 | CODE_GEN_PKG=${CODE_GEN_PKG:-$GOPATH/pkg/mod/k8s.io/code-generator\@${CODE_GEN_VERSION}} 24 | chmod +x ${CODE_GEN_PKG}/kube_codegen.sh 25 | 26 | AUTHENTICATOR_ROOT="${SCRIPT_ROOT}/pkg/mapper/crd" 27 | AUTHENTICATOR_PKG="sigs.k8s.io/aws-iam-authenticator" 28 | 29 | source "${CODE_GEN_PKG}/kube_codegen.sh" 30 | 31 | kube::codegen::gen_helpers \ 32 | --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ 33 | "${AUTHENTICATOR_ROOT}/apis" \ 34 | 35 | kube::codegen::gen_client \ 36 | --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ 37 | --output-dir "${AUTHENTICATOR_ROOT}/generated" \ 38 | --output-pkg "${AUTHENTICATOR_PKG}/pkg/mapper/crd/generated" \ 39 | --with-watch \ 40 | "${AUTHENTICATOR_ROOT}/apis" \ 41 | -------------------------------------------------------------------------------- /hack/update-gomod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | set -euo pipefail 19 | set -x 20 | 21 | VERSION=${1#"v"} 22 | if [ -z "$VERSION" ]; then 23 | echo "Must specify version!" 24 | exit 1 25 | fi 26 | MODS=($( 27 | curl -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${VERSION}/go.mod | 28 | sed -n 's|.*k8s.io/\(.*\) => ./staging/src/k8s.io/.*|k8s.io/\1|p' 29 | )) 30 | echo $MODS 31 | for MOD in "${MODS[@]}"; do 32 | echo $MOD 33 | V=$( 34 | go mod download -json "${MOD}@kubernetes-${VERSION}" | 35 | sed -n 's|.*"Version": "\(.*\)".*|\1|p' 36 | ) 37 | go mod edit "-replace=${MOD}=${MOD}@${V}" 38 | done 39 | 40 | -------------------------------------------------------------------------------- /pkg/arn/arn.go: -------------------------------------------------------------------------------- 1 | package arn 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | awsarn "github.com/aws/aws-sdk-go/aws/arn" 8 | "github.com/aws/aws-sdk-go/aws/endpoints" 9 | ) 10 | 11 | type PrincipalType int 12 | 13 | const ( 14 | // Supported principals 15 | NONE PrincipalType = iota 16 | ROLE 17 | USER 18 | ROOT 19 | FEDERATED_USER 20 | ASSUMED_ROLE 21 | ) 22 | 23 | // Canonicalize validates IAM resources are appropriate for the authenticator 24 | // and converts STS assumed roles into the IAM role resource. 25 | // 26 | // Supported IAM resources are: 27 | // - AWS root user: arn:aws:iam::123456789012:root 28 | // - IAM user: arn:aws:iam::123456789012:user/Bob 29 | // - IAM role: arn:aws:iam::123456789012:role/S3Access 30 | // - IAM Assumed role: arn:aws:sts::123456789012:assumed-role/Accounting-Role/Mary (converted to IAM role) 31 | // - Federated user: arn:aws:sts::123456789012:federated-user/Bob 32 | func Canonicalize(arn string) (PrincipalType, string, error) { 33 | parsed, err := awsarn.Parse(arn) 34 | if err != nil { 35 | return NONE, "", fmt.Errorf("arn '%s' is invalid: '%v'", arn, err) 36 | } 37 | 38 | if err := checkPartition(parsed.Partition); err != nil { 39 | return NONE, "", fmt.Errorf("arn '%s' does not have a recognized partition", arn) 40 | } 41 | 42 | parts := strings.Split(parsed.Resource, "/") 43 | resource := parts[0] 44 | 45 | switch parsed.Service { 46 | case "sts": 47 | switch resource { 48 | case "federated-user": 49 | return FEDERATED_USER, arn, nil 50 | case "assumed-role": 51 | if len(parts) < 3 { 52 | return NONE, "", fmt.Errorf("assumed-role arn '%s' does not have a role", arn) 53 | } 54 | // IAM ARNs can contain paths, part[0] is resource, parts[len(parts)] is the SessionName. 55 | role := strings.Join(parts[1:len(parts)-1], "/") 56 | return ASSUMED_ROLE, fmt.Sprintf("arn:%s:iam::%s:role/%s", parsed.Partition, parsed.AccountID, role), nil 57 | default: 58 | return NONE, "", fmt.Errorf("unrecognized resource %s for service sts", parsed.Resource) 59 | } 60 | case "iam": 61 | switch resource { 62 | case "role": 63 | return ROLE, arn, nil 64 | case "user": 65 | return USER, arn, nil 66 | case "root": 67 | return ROOT, arn, nil 68 | default: 69 | return NONE, "", fmt.Errorf("unrecognized resource %s for service iam", parsed.Resource) 70 | } 71 | } 72 | 73 | return NONE, "", fmt.Errorf("service %s in arn %s is not a valid service for identities", parsed.Service, arn) 74 | } 75 | 76 | // TODO: add strip path functionality Canonicalize after testing it in all mappers - this can be used to support role paths in the configmap 77 | func StripPath(arn string) (string, error) { 78 | parsed, err := awsarn.Parse(arn) 79 | if err != nil { 80 | return "", fmt.Errorf("arn '%s' is invalid: '%v'", arn, err) 81 | } 82 | 83 | if err := checkPartition(parsed.Partition); err != nil { 84 | return "", fmt.Errorf("arn '%s' does not have a recognized partition", arn) 85 | } 86 | 87 | parts := strings.Split(parsed.Resource, "/") 88 | resource := parts[0] 89 | 90 | if resource != "role" { 91 | return arn, nil 92 | } 93 | 94 | if len(parts) > 2 { 95 | // Stripping off the path means we just need to keep the first and last part of the arn resource 96 | // role/path/for/this-role/matt -> role/matt 97 | role := parts[len(parts)-1] 98 | return fmt.Sprintf("arn:%s:iam::%s:role/%s", parsed.Partition, parsed.AccountID, role), nil 99 | } 100 | return arn, nil 101 | } 102 | 103 | func checkPartition(partition string) error { 104 | for _, p := range endpoints.DefaultPartitions() { 105 | if partition == p.ID() { 106 | return nil 107 | } 108 | } 109 | return fmt.Errorf("partition %s is not recognized", partition) 110 | } 111 | -------------------------------------------------------------------------------- /pkg/arn/arn_test.go: -------------------------------------------------------------------------------- 1 | package arn 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var arnTests = []struct { 9 | arn string // input arn 10 | expected string // canonacalized arn 11 | err error // expected error value 12 | }{ 13 | {"NOT AN ARN", "", fmt.Errorf("Not an arn")}, 14 | {"arn:aws:iam::123456789012:user/Alice", "arn:aws:iam::123456789012:user/Alice", nil}, 15 | {"arn:aws:iam::123456789012:role/Users", "arn:aws:iam::123456789012:role/Users", nil}, 16 | {"arn:aws:sts::123456789012:assumed-role/Admin/Session", "arn:aws:iam::123456789012:role/Admin", nil}, 17 | {"arn:aws:sts::123456789012:federated-user/Bob", "arn:aws:sts::123456789012:federated-user/Bob", nil}, 18 | {"arn:aws:iam::123456789012:root", "arn:aws:iam::123456789012:root", nil}, 19 | {"arn:aws:sts::123456789012:assumed-role/Org/Team/Admin/Session", "arn:aws:iam::123456789012:role/Org/Team/Admin", nil}, 20 | {"arn:aws-iso:iam::123456789012:user/Chris", "arn:aws-iso:iam::123456789012:user/Chris", nil}, 21 | {"arn:aws-iso-b:iam::123456789012:user/Chris", "arn:aws-iso-b:iam::123456789012:user/Chris", nil}, 22 | } 23 | 24 | func TestUserARN(t *testing.T) { 25 | for _, tc := range arnTests { 26 | _, actual, err := Canonicalize(tc.arn) 27 | if err != nil && tc.err == nil || err == nil && tc.err != nil { 28 | t.Errorf("Canoncialize(%s) expected err: %v, actual err: %v", tc.arn, tc.err, err) 29 | continue 30 | } 31 | if actual != tc.expected { 32 | t.Errorf("Canonicalize(%s) expected: %s, actual: %s", tc.arn, tc.expected, actual) 33 | } 34 | } 35 | } 36 | 37 | var arnStripTests = []struct { 38 | arn string // input arn 39 | expected string // canonacalized arn 40 | err error // expected error value 41 | }{ 42 | {"NOT AN ARN", "", fmt.Errorf("Not an arn")}, 43 | {"arn:aws:iam::123456789012:role/Org/Team/Admin", "arn:aws:iam::123456789012:role/Admin", nil}, 44 | {"arn:aws:iam::123456789012:role/Admin", "arn:aws:iam::123456789012:role/Admin", nil}, 45 | {"arn:aws:iam::123456789012:user/Alice", "arn:aws:iam::123456789012:user/Alice", nil}, 46 | {"arn:aws:sts::123456789012:federated-user/Bob", "arn:aws:sts::123456789012:federated-user/Bob", nil}, 47 | } 48 | 49 | func TestStripPath(t *testing.T) { 50 | for _, tc := range arnStripTests { 51 | actual, err := StripPath(tc.arn) 52 | if err != nil && tc.err == nil || err == nil && tc.err != nil { 53 | t.Errorf("stripPath(%s) expected err: %v, actual err: %v", tc.arn, tc.err, err) 54 | continue 55 | } 56 | if actual != tc.expected { 57 | t.Errorf("stripPath(%s) expected: %s, actual: %s", tc.arn, tc.expected, actual) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/arn/arnlike.go: -------------------------------------------------------------------------------- 1 | package arn 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | arnDelimiter = ":" 11 | arnSectionsExpected = 6 12 | arnPrefix = "arn:" 13 | 14 | // zero-indexed 15 | sectionPartition = 1 16 | sectionService = 2 17 | sectionRegion = 3 18 | sectionAccountID = 4 19 | sectionResource = 5 20 | 21 | // errors 22 | invalidPrefix = "invalid prefix" 23 | invalidSections = "not enough sections" 24 | ) 25 | 26 | // ArnLike takes an ARN and returns true if it is matched by the pattern. 27 | // Each component of the ARN is matched individually as per 28 | // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_ARN 29 | func ArnLike(arn, pattern string) (bool, error) { 30 | // "parse" the input arn into sections 31 | arnSections, err := parse(arn) 32 | if err != nil { 33 | return false, fmt.Errorf("Could not parse input arn: %v", err) 34 | } 35 | patternSections, err := parse(pattern) 36 | if err != nil { 37 | return false, fmt.Errorf("Could not parse ArnLike string: %v", err) 38 | } 39 | 40 | // Tidy regexp special characters. Escape the ones not used in ArnLike. 41 | // Replace multiple * with .* - we're assuming `\` is not allowed in ARNs 42 | preparePatternSections(patternSections) 43 | 44 | for index := range arnSections { 45 | patternGlob, err := regexp.Compile(patternSections[index]) 46 | if err != nil { 47 | return false, fmt.Errorf("Could not parse %s: %v", patternSections[index], err) 48 | } 49 | 50 | if !patternGlob.MatchString(arnSections[index]) { 51 | return false, nil 52 | } 53 | } 54 | 55 | return true, nil 56 | } 57 | 58 | // parse is a copy of arn.Parse from the AWS SDK but represents the ARN as []string 59 | func parse(input string) ([]string, error) { 60 | if !strings.HasPrefix(input, arnPrefix) { 61 | return nil, fmt.Errorf(invalidPrefix) 62 | } 63 | arnSections := strings.SplitN(input, arnDelimiter, arnSectionsExpected) 64 | if len(arnSections) != arnSectionsExpected { 65 | return nil, fmt.Errorf(invalidSections) 66 | } 67 | 68 | return arnSections, nil 69 | } 70 | 71 | // preparePatternSections goes through each section of the arnLike slice and escapes any meta characters, except for 72 | // `*` and `?` which are replaced by `.*` and `.?` respectively. ^ and $ are added as we require an exact match 73 | func preparePatternSections(arnLikeSlice []string) { 74 | for index, section := range arnLikeSlice { 75 | quotedString := quoteMeta(section) 76 | arnLikeSlice[index] = `^` + quotedString + `$` 77 | } 78 | } 79 | 80 | // the below is based on regexp.QuoteMeta to escape metacharacters except for `?` and `*`, changing them to `*` and `.*` 81 | 82 | // quoteMeta returns a string that escapes all regular expression metacharacters 83 | // inside the argument text; the returned string is a regular expression matching 84 | // the literal text. 85 | func quoteMeta(s string) string { 86 | const specialChars = `\.+()|[]{}^$` 87 | 88 | var i int 89 | b := make([]byte, 2*len(s)-i) 90 | copy(b, s[:i]) 91 | j := i 92 | for ; i < len(s); i++ { 93 | if strings.Contains(specialChars, s[i:i+1]) { 94 | b[j] = '\\' 95 | j++ 96 | } else if s[i] == '*' || s[i] == '?' { 97 | b[j] = '.' 98 | j++ 99 | } 100 | b[j] = s[i] 101 | j++ 102 | } 103 | return string(b[:j]) 104 | } 105 | -------------------------------------------------------------------------------- /pkg/config/certs/certs_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 by the contributors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package certs 18 | 19 | import ( 20 | "bytes" 21 | "crypto/x509" 22 | "net" 23 | "sort" 24 | "testing" 25 | "time" 26 | ) 27 | 28 | func ipsEqual(a, b []net.IP) bool { 29 | if len(a) != len(b) { 30 | return false 31 | } 32 | for i := range a { 33 | if !a[i].Equal(b[i]) { 34 | return false 35 | } 36 | } 37 | return true 38 | } 39 | 40 | func stringsEqual(a, b []string) bool { 41 | if len(a) != len(b) { 42 | return false 43 | } 44 | for i := range a { 45 | if a[i] != b[i] { 46 | return false 47 | } 48 | } 49 | return true 50 | } 51 | 52 | func TestSelfSignCert(t *testing.T) { 53 | certLifetime := time.Hour * 24 * 365 * 100 54 | tests := []struct { 55 | opts CertificateOptions 56 | err error 57 | dnsNames []string 58 | ips []net.IP 59 | }{ 60 | { 61 | opts: CertificateOptions{ 62 | Address: "127.0.0.1", 63 | Hostname: "127.0.0.1", 64 | Lifetime: certLifetime, 65 | }, 66 | dnsNames: []string{}, 67 | ips: []net.IP{net.IPv4(127, 0, 0, 1)}, 68 | }, 69 | { 70 | opts: CertificateOptions{ 71 | Address: "192.0.2.1", 72 | Hostname: "example.com", 73 | Lifetime: certLifetime, 74 | }, 75 | dnsNames: []string{"example.com"}, 76 | ips: []net.IP{net.IPv4(192, 0, 2, 1)}, 77 | }, 78 | { 79 | opts: CertificateOptions{ 80 | Address: "::", 81 | Hostname: "2001:db8::1:0", 82 | Lifetime: certLifetime, 83 | }, 84 | dnsNames: []string{}, 85 | ips: []net.IP{net.ParseIP("2001:db8::1:0")}, 86 | }, 87 | } 88 | 89 | for _, test := range tests { 90 | certBytes, keyBytes, err := selfSignedCertificate(test.opts.Address, test.opts.Hostname, test.opts.Lifetime) 91 | if err != nil { 92 | if err != test.err { 93 | t.Errorf("Expected error %v, got %v", test.err, err) 94 | } 95 | continue 96 | } 97 | 98 | cert, err := x509.ParseCertificate(certBytes) 99 | if err != nil { 100 | t.Fatalf("ParseCertificate: %v", err) 101 | } 102 | 103 | key, err := x509.ParsePKCS1PrivateKey(keyBytes) 104 | if err != nil { 105 | t.Fatalf("ParsePKCS1PrivateKey: %v", err) 106 | } 107 | if err := key.Validate(); err != nil { 108 | t.Errorf("private key is invalid: %v", err) 109 | } 110 | 111 | dnsNames := cert.DNSNames 112 | sort.Strings(dnsNames) 113 | if !stringsEqual(dnsNames, test.dnsNames) { 114 | t.Errorf("expected DNSNames %v, got %v", test.dnsNames, dnsNames) 115 | } 116 | 117 | ips := cert.IPAddresses 118 | sort.Slice(ips, func(i, j int) bool { 119 | return bytes.Compare(ips[i].To16(), ips[j].To16()) == -1 120 | }) 121 | if !ipsEqual(ips, test.ips) { 122 | t.Errorf("expected IPs %v, got %v", test.ips, ips) 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 by the contributors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "crypto/tls" 21 | "fmt" 22 | "net" 23 | "net/url" 24 | "path/filepath" 25 | "strconv" 26 | 27 | "sigs.k8s.io/aws-iam-authenticator/pkg/config/certs" 28 | "sigs.k8s.io/aws-iam-authenticator/pkg/config/kubeconfig" 29 | ) 30 | 31 | // ServerURL returns the URL to connect to this server. 32 | func (c *Config) ServerURL() string { 33 | u := url.URL{ 34 | Scheme: "https", 35 | Host: c.ServerAddr(), 36 | Path: "/authenticate", 37 | } 38 | return u.String() 39 | } 40 | 41 | // ServerAddr returns the host and port clients should use for server endpoint. 42 | func (c *Config) ServerAddr() string { 43 | return net.JoinHostPort(c.Hostname, strconv.Itoa(c.HostPort)) 44 | } 45 | 46 | // ListenAddr returns the IP address and port mapping to bind with 47 | func (c *Config) ListenAddr() string { 48 | return net.JoinHostPort(c.Address, strconv.Itoa(c.HostPort)) 49 | } 50 | 51 | // GenerateFiles will generate the certificate and private key and then create the kubeconfig 52 | func (c *Config) GenerateFiles() error { 53 | // load or generate a certificate+private key 54 | _, err := c.GetOrCreateX509KeyPair() 55 | if err != nil { 56 | return fmt.Errorf("could not load/generate a certificate") 57 | } 58 | err = c.GenerateWebhookKubeconfig() 59 | if err != nil { 60 | return fmt.Errorf("could not generate a webhook kubeconfig at %s: %v", c.GenerateKubeconfigPath, err) 61 | } 62 | return nil 63 | } 64 | 65 | func (c *Config) GenerateWebhookKubeconfig() error { 66 | cert, err := certs.LoadX509KeyPair(c.CertPath(), c.KeyPath()) 67 | if err != nil { 68 | return fmt.Errorf("failed to load an existing certificate: %v", err) 69 | } 70 | 71 | return kubeconfig.CreateWebhookKubeconfig(cert, c.GenerateKubeconfigPath, c.ServerURL()) 72 | } 73 | 74 | // CertPath returns the path to the pem file containing the certificate 75 | func (c *Config) CertPath() string { 76 | return filepath.Join(c.StateDir, "cert.pem") 77 | } 78 | 79 | // KeyPath returns the path to the pem file containing the private key 80 | func (c *Config) KeyPath() string { 81 | return filepath.Join(c.StateDir, "key.pem") 82 | } 83 | 84 | func (c *Config) CertOpts() certs.CertificateOptions { 85 | return certs.CertificateOptions{ 86 | CertPath: c.CertPath(), 87 | KeyPath: c.KeyPath(), 88 | Hostname: c.Hostname, 89 | Address: c.Address, 90 | Lifetime: certLifetime, 91 | } 92 | } 93 | 94 | // GetOrCreateCertificate will create a certificate if it cannot find one based on the config 95 | func (c *Config) GetOrCreateX509KeyPair() (*tls.Certificate, error) { 96 | return certs.GetOrCreateX509KeyPair(c.CertOpts()) 97 | } 98 | -------------------------------------------------------------------------------- /pkg/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestServerUrl(t *testing.T) { 8 | tests := []struct { 9 | config Config 10 | expected string 11 | }{ 12 | { 13 | config: Config{ 14 | Hostname: "example.com", 15 | HostPort: 6443, 16 | }, 17 | expected: "https://example.com:6443/authenticate", 18 | }, 19 | { 20 | config: Config{ 21 | Hostname: "127.0.0.1", 22 | HostPort: 8080, 23 | }, 24 | expected: "https://127.0.0.1:8080/authenticate", 25 | }, 26 | { 27 | config: Config{ 28 | Hostname: "2001:db8::1:0", 29 | HostPort: 1234, 30 | }, 31 | expected: "https://[2001:db8::1:0]:1234/authenticate", 32 | }, 33 | } 34 | 35 | for _, test := range tests { 36 | actual := test.config.ServerURL() 37 | if actual != test.expected { 38 | t.Errorf("Expected %q, got %q", test.expected, actual) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/config/constants.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 by the contributors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import "time" 20 | 21 | const ( 22 | // certFilename is the filename (under the StateDir) where the self-signed 23 | // CA certificate will be stored. 24 | certFilename = "cert.pem" 25 | 26 | // keyFilename is the filename (under the StateDir) where the private key 27 | // will be stored. 28 | keyFilename = "key.pem" 29 | 30 | // certLifetime is the lifetime of the CA certificate (100 years) 31 | certLifetime = time.Hour * 24 * 365 * 100 32 | ) 33 | -------------------------------------------------------------------------------- /pkg/config/features.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 by the contributors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "k8s.io/component-base/featuregate" 21 | ) 22 | 23 | const ( 24 | // ConfiguredInitDirectories enables placing files directly in configured 25 | // directories with init 26 | ConfiguredInitDirectories featuregate.Feature = "ConfiguredInitDirectories" 27 | // IAMIdentityMappingCRD enables using CRDs to manage allowed users 28 | IAMIdentityMappingCRD featuregate.Feature = "IAMIdentityMappingCRD" 29 | // SSORoleMatch enables matching roles managed by AWS SSO, with handling 30 | // for their randomly generated suffixes 31 | SSORoleMatch featuregate.Feature = "SSORoleMatch" 32 | ) 33 | 34 | var ( 35 | SSORoleMatchEnabled bool 36 | ) 37 | 38 | var DefaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ 39 | ConfiguredInitDirectories: {Default: false, PreRelease: featuregate.Alpha}, 40 | IAMIdentityMappingCRD: {Default: false, PreRelease: featuregate.Alpha}, 41 | SSORoleMatch: {Default: false, PreRelease: featuregate.Alpha}, 42 | } 43 | -------------------------------------------------------------------------------- /pkg/config/kubeconfig/kubeconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 by the contributors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubeconfig 18 | 19 | import ( 20 | "crypto/tls" 21 | "os" 22 | "text/template" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "sigs.k8s.io/aws-iam-authenticator/pkg/config/certs" 27 | ) 28 | 29 | type KubeconfigParams struct { 30 | ServerURL string 31 | CertificateAuthorityBase64 string 32 | Token string 33 | } 34 | 35 | // CreateWebhookKubeconfig will create a kubeconfig for the webhook server 36 | func CreateWebhookKubeconfig(cert *tls.Certificate, kubeconfigPath, serverURL string) error { 37 | logrus.WithField("kubeconfigPath", kubeconfigPath).Info("writing webhook kubeconfig file") 38 | 39 | return KubeconfigParams{ 40 | ServerURL: serverURL, 41 | CertificateAuthorityBase64: certs.CertToPEMBase64(cert.Certificate[0]), 42 | }.WriteKubeconfig(kubeconfigPath, webhookKubeconfigTemplate) 43 | } 44 | 45 | func (p KubeconfigParams) WriteKubeconfig(outputPath string, t *template.Template) error { 46 | f, err := os.Create(outputPath) 47 | if err != nil { 48 | return err 49 | } 50 | defer f.Close() 51 | return t.Execute(f, p) 52 | } 53 | 54 | var webhookKubeconfigTemplate = template.Must( 55 | template.New("webhook.kubeconfig").Option("missingkey=error").Parse(` 56 | clusters: 57 | - name: aws-iam-authenticator 58 | cluster: 59 | certificate-authority-data: {{.CertificateAuthorityBase64}} 60 | server: {{.ServerURL}} 61 | # user refers to the API server client 62 | users: 63 | - name: apiserver 64 | current-context: webhook 65 | contexts: 66 | - name: webhook 67 | context: 68 | cluster: aws-iam-authenticator 69 | user: apiserver 70 | `)) 71 | -------------------------------------------------------------------------------- /pkg/config/mapper.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "sigs.k8s.io/aws-iam-authenticator/pkg/arn" 9 | "sigs.k8s.io/aws-iam-authenticator/pkg/token" 10 | 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // SSOArnLike returns a string that can be passed to arnlike.ArnLike to 15 | // match canonicalized IAM Role ARNs against. Assumes Validate() has been called. 16 | func (m *RoleMapping) SSOArnLike() string { 17 | if m.SSO == nil { 18 | return "" 19 | } 20 | 21 | var partition string 22 | if m.SSO.Partition == "" { 23 | partition = "aws" 24 | } 25 | 26 | return strings.ToLower(fmt.Sprintf("arn:%s:iam::%s:role/AWSReservedSSO_%s_*", partition, m.SSO.AccountID, m.SSO.PermissionSetName)) 27 | } 28 | 29 | // Validate returns an error if the RoleMapping is not valid after being unmarshaled 30 | func (m *RoleMapping) Validate() error { 31 | if m == nil { 32 | return fmt.Errorf("RoleMapping is nil") 33 | } 34 | 35 | if m.RoleARN == "" && m.SSO == nil { 36 | return fmt.Errorf("One of rolearn or SSO must be supplied") 37 | } else if m.RoleARN != "" && m.SSO != nil { 38 | return fmt.Errorf("Only one of rolearn or SSO can be supplied") 39 | } 40 | 41 | if m.SSO != nil { 42 | accountIDRegexp := regexp.MustCompile("^[0-9]{12}$") 43 | if !accountIDRegexp.MatchString(m.SSO.AccountID) { 44 | return fmt.Errorf("AccountID '%s' is not a valid AWS Account ID", m.SSO.AccountID) 45 | } 46 | 47 | // https://docs.aws.amazon.com/singlesignon/latest/APIReference/API_PermissionSet.html 48 | permissionSetNameRegexp := regexp.MustCompile(`^[\w+=,.@-]{1,32}$`) 49 | if !permissionSetNameRegexp.MatchString(m.SSO.PermissionSetName) { 50 | return fmt.Errorf("PermissionSetName '%s' is not a valid AWS SSO PermissionSet Name", m.SSO.PermissionSetName) 51 | } 52 | 53 | switch m.SSO.Partition { 54 | case "aws", "aws-cn", "aws-us-gov", "aws-iso", "aws-iso-b": 55 | // valid 56 | case "": 57 | // treated as "aws" 58 | default: 59 | return fmt.Errorf("Partition '%s' is not a valid AWS partition", m.SSO.Partition) 60 | } 61 | 62 | ssoArnLikeString := m.SSOArnLike() 63 | ok, err := arn.ArnLike(ssoArnLikeString, "arn:*:iam:*:*:role/*") 64 | if err != nil { 65 | return fmt.Errorf("SSOArnLike '%s' is not valid: %v", ssoArnLikeString, err) 66 | } else if !ok { 67 | return fmt.Errorf("SSOArnLike '%s' did not match an ARN for a canonicalized IAM Role", ssoArnLikeString) 68 | } 69 | } 70 | 71 | return nil 72 | } 73 | 74 | // Matches returns true if the supplied ARN or SSO settings matches 75 | // this RoleMapping 76 | func (m *RoleMapping) Matches(subject string) bool { 77 | if m.RoleARN != "" { 78 | return strings.ToLower(m.RoleARN) == strings.ToLower(subject) 79 | } 80 | 81 | // Assume the caller has called Validate(), which parses m.RoleARNLike 82 | // If subject is not parsable, then it cannot be a valid ARN anyway so 83 | // we can ignore the error here 84 | var ok bool 85 | if SSORoleMatchEnabled { 86 | var err error 87 | ok, err = arn.ArnLike(subject, m.SSOArnLike()) 88 | if err != nil { 89 | logrus.Error("Could not parse subject ARN: ", err) 90 | } 91 | } 92 | return ok 93 | } 94 | 95 | // Key returns RoleARN or SSOArnLike(), whichever is not empty. 96 | // Used to get a Key name for map[string]RoleMapping 97 | func (m *RoleMapping) Key() string { 98 | if m.RoleARN != "" { 99 | return strings.ToLower(m.RoleARN) 100 | } 101 | return m.SSOArnLike() 102 | } 103 | 104 | // IdentityMapping converts the RoleMapping into a generic IdentityMapping object 105 | func (m *RoleMapping) IdentityMapping(identity *token.Identity) *IdentityMapping { 106 | return &IdentityMapping{ 107 | IdentityARN: strings.ToLower(identity.CanonicalARN), 108 | Username: m.Username, 109 | Groups: m.Groups, 110 | } 111 | } 112 | 113 | // Validate returns an error if the UserMapping is not valid after being unmarshaled 114 | func (m *UserMapping) Validate() error { 115 | if m == nil { 116 | return fmt.Errorf("UserMapping is nil") 117 | } 118 | 119 | if m.UserARN == "" { 120 | return fmt.Errorf("Value for userarn must be supplied") 121 | } 122 | 123 | return nil 124 | } 125 | 126 | // Matches returns true if the supplied ARN string matche this UserMapping 127 | func (m *UserMapping) Matches(subject string) bool { 128 | return strings.ToLower(m.UserARN) == strings.ToLower(subject) 129 | } 130 | 131 | // Key returns UserARN. 132 | // Used to get a Key name for map[string]UserMapping 133 | func (m *UserMapping) Key() string { 134 | return m.UserARN 135 | } 136 | 137 | // IdentityMapping converts the UserMapping into a generic IdentityMapping object 138 | func (m *UserMapping) IdentityMapping(identity *token.Identity) *IdentityMapping { 139 | return &IdentityMapping{ 140 | IdentityARN: strings.ToLower(identity.CanonicalARN), 141 | Username: m.Username, 142 | Groups: m.Groups, 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /pkg/errutil/errors.go: -------------------------------------------------------------------------------- 1 | package errutil 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ErrNotMapped = errors.New("Identity is not mapped") 8 | 9 | var ErrIDAndARNMismatch = errors.New("ARN does not match User ID") 10 | -------------------------------------------------------------------------------- /pkg/filecache/converter.go: -------------------------------------------------------------------------------- 1 | package filecache 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go/aws/credentials" 8 | ) 9 | 10 | type v2 struct { 11 | creds *credentials.Credentials 12 | } 13 | 14 | var _ aws.CredentialsProvider = &v2{} 15 | 16 | func (p *v2) Retrieve(ctx context.Context) (aws.Credentials, error) { 17 | val, err := p.creds.GetWithContext(ctx) 18 | if err != nil { 19 | return aws.Credentials{}, err 20 | } 21 | resp := aws.Credentials{ 22 | AccessKeyID: val.AccessKeyID, 23 | SecretAccessKey: val.SecretAccessKey, 24 | SessionToken: val.SessionToken, 25 | Source: val.ProviderName, 26 | CanExpire: false, 27 | // Don't have account ID 28 | } 29 | 30 | if expiration, err := p.creds.ExpiresAt(); err == nil { 31 | resp.CanExpire = true 32 | resp.Expires = expiration 33 | } 34 | return resp, nil 35 | } 36 | 37 | // V1ProviderToV2Provider converts a v1 credentials.Provider to a v2 aws.CredentialsProvider 38 | func V1ProviderToV2Provider(p credentials.Provider) aws.CredentialsProvider { 39 | return V1CredentialToV2Provider(credentials.NewCredentials(p)) 40 | } 41 | 42 | // V1CredentialToV2Provider converts a v1 credentials.Credential to a v2 aws.CredentialProvider 43 | func V1CredentialToV2Provider(c *credentials.Credentials) aws.CredentialsProvider { 44 | return &v2{creds: c} 45 | } 46 | 47 | // V2CredentialToV1Value converts a v2 aws.Credentials to a v1 credentials.Value 48 | func V2CredentialToV1Value(cred aws.Credentials) credentials.Value { 49 | return credentials.Value{ 50 | AccessKeyID: cred.AccessKeyID, 51 | SecretAccessKey: cred.SecretAccessKey, 52 | SessionToken: cred.SessionToken, 53 | ProviderName: cred.Source, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pkg/fileutil/util.go: -------------------------------------------------------------------------------- 1 | package fileutil 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/fsnotify/fsnotify" 10 | "github.com/sirupsen/logrus" 11 | "k8s.io/apimachinery/pkg/util/wait" 12 | "sigs.k8s.io/aws-iam-authenticator/pkg/metrics" 13 | ) 14 | 15 | type FileChangeCallBack interface { 16 | CallBackForFileLoad(dynamicContent []byte) error 17 | CallBackForFileDeletion() error 18 | } 19 | 20 | func waitUntilFileAvailable(filename string, stopCh <-chan struct{}) { 21 | if _, err := os.Stat(filename); err == nil { 22 | return 23 | } 24 | ticker := time.NewTicker(1 * time.Second) 25 | defer ticker.Stop() 26 | for { 27 | select { 28 | case <-stopCh: 29 | logrus.Infof("startLoadDynamicFile: waitUntilFileAvailable exit because get stopCh, filename is %s", filename) 30 | return 31 | case <-ticker.C: 32 | if _, err := os.Stat(filename); err == nil { 33 | return 34 | } 35 | } 36 | } 37 | } 38 | 39 | func loadDynamicFile(filename string, stopCh <-chan struct{}) ([]byte, error) { 40 | waitUntilFileAvailable(filename, stopCh) 41 | if content, err := os.ReadFile(filename); err == nil { 42 | logrus.Infof("LoadDynamicFile: %v is available. content is %s", filename, string(content)) 43 | return content, nil 44 | } else { 45 | return nil, err 46 | } 47 | } 48 | 49 | func StartLoadDynamicFile(filename string, callBack FileChangeCallBack, stopCh <-chan struct{}) { 50 | go wait.Until(func() { 51 | // start to watch the file change 52 | watcher, err := fsnotify.NewWatcher() 53 | if err != nil { 54 | logrus.Errorf("startLoadDynamicFile: failed when call fsnotify.NewWatcher, %+v", err) 55 | metrics.Get().DynamicFileFailures.Inc() 56 | return 57 | } 58 | defer watcher.Close() 59 | content, err := loadDynamicFile(filename, stopCh) 60 | if err != nil { 61 | logrus.Errorf("startLoadDynamicFile: failed when loading file %s, %v", filename, err) 62 | return 63 | } 64 | err = watcher.Add(filename) 65 | if err != nil { 66 | logrus.Errorf("startLoadDynamicFile: could not add file to watcher %v", err) 67 | metrics.Get().DynamicFileFailures.Inc() 68 | return 69 | } 70 | if err := callBack.CallBackForFileLoad(content); err != nil { 71 | logrus.Errorf("StartLoadDynamicFile: error in callBackForFileLoad, %v", err) 72 | return 73 | } 74 | for { 75 | select { 76 | case <-stopCh: 77 | logrus.Infof("startLoadDynamicFile: watching exit because stopCh closed, filename is %s", filename) 78 | return 79 | case event := <-watcher.Events: 80 | switch { 81 | case event.Op&fsnotify.Write == fsnotify.Write, event.Op&fsnotify.Create == fsnotify.Create: 82 | // reload the access entry file 83 | logrus.Info("startLoadDynamicFile: got WRITE/CREATE event reload it the memory") 84 | content, err := loadDynamicFile(filename, stopCh) 85 | if err != nil { 86 | logrus.Errorf("StartLoadDynamicFile: error in loadDynamicFile, %v", err) 87 | return 88 | } 89 | if err := callBack.CallBackForFileLoad(content); err != nil { 90 | logrus.Errorf("StartLoadDynamicFile: error in callBackForFileLoad, %v", err) 91 | } 92 | case event.Op&fsnotify.Rename == fsnotify.Rename, event.Op&fsnotify.Remove == fsnotify.Remove: 93 | logrus.Info("startLoadDynamicFile: got RENAME/REMOVE event") 94 | // test if the "REMOVE" is triggered by vi or cp cmd 95 | _, err := os.Stat(filename) 96 | if os.IsNotExist(err) { 97 | if err := callBack.CallBackForFileDeletion(); err != nil { 98 | logrus.Errorf("StartLoadDynamicFile: error in callBackForFileDeletion, %v", err) 99 | } 100 | } 101 | return 102 | } 103 | case err := <-watcher.Errors: 104 | logrus.Errorf("startLoadDynamicFile: watcher.Errors for dynamic file %v", err) 105 | metrics.Get().DynamicFileFailures.Inc() 106 | return 107 | } 108 | } 109 | }, time.Second, stopCh) 110 | } 111 | 112 | func CalculateTimeDeltaFromUnixInSeconds(from string) (int64, error) { 113 | startTime, err := strconv.ParseInt(from, 10, 64) 114 | if err != nil { 115 | return 0, fmt.Errorf("failed to parse 'startTime' string: %v", err) 116 | } 117 | 118 | endTime := time.Now().Unix() 119 | 120 | if startTime > endTime { 121 | return 0, fmt.Errorf("start timestamp is after end timestamp") 122 | } 123 | 124 | return endTime - startTime, nil 125 | } 126 | -------------------------------------------------------------------------------- /pkg/httputil/client.go: -------------------------------------------------------------------------------- 1 | // Package httputil implements HTTP utilities. 2 | package httputil 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | 8 | "golang.org/x/time/rate" 9 | ) 10 | 11 | // NewRateLimitedClient returns a new HTTP client with rate limiter. 12 | func NewRateLimitedClient(qps int, burst int) (*http.Client, error) { 13 | if qps == 0 { 14 | return http.DefaultClient, nil 15 | } 16 | if burst < 1 { 17 | return nil, fmt.Errorf("burst expected >0, got %d", burst) 18 | } 19 | return &http.Client{ 20 | Transport: &rateLimitedRoundTripper{ 21 | rt: http.DefaultTransport, 22 | rl: rate.NewLimiter(rate.Limit(qps), burst), 23 | }, 24 | }, nil 25 | } 26 | 27 | type rateLimitedRoundTripper struct { 28 | rt http.RoundTripper 29 | rl *rate.Limiter 30 | } 31 | 32 | func (rr *rateLimitedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 33 | if err := rr.rl.Wait(req.Context()); err != nil { 34 | return nil, err 35 | } 36 | return rr.rt.RoundTrip(req) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/httputil/client_test.go: -------------------------------------------------------------------------------- 1 | package httputil 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "strings" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestNewRateLimitedClient(t *testing.T) { 14 | mux := http.NewServeMux() 15 | mux.HandleFunc("/test", testHandler) 16 | 17 | ts := httptest.NewServer(mux) 18 | defer ts.Close() 19 | 20 | u := ts.URL + "/test" 21 | 22 | // requests are to be throttled if qps+burst < reqs 23 | // estimated time: reqs / (qps+burst) seconds 24 | tbs := []struct { 25 | ctxTimeout time.Duration 26 | qps int 27 | burst int 28 | requests int // concurrent requests 29 | err string 30 | }{ 31 | { 32 | qps: 1, 33 | burst: 1, 34 | requests: 10, 35 | }, 36 | { 37 | qps: 15, 38 | burst: 5, 39 | requests: 100, 40 | }, 41 | { 42 | qps: 8, 43 | burst: 2, 44 | requests: 20, 45 | }, 46 | { 47 | // 20 concurrent ec2 API requests should exceed 1 QPS before 10ms 48 | // thus rate limiter returns an error 49 | ctxTimeout: 10 * time.Millisecond, 50 | qps: 1, 51 | burst: 1, 52 | requests: 20, 53 | err: `context deadline`, 54 | // "Wait(n=1) would exceed context deadline" for requests before timeout 55 | // "context deadline exceeded" for requests after timeout 56 | }, 57 | } 58 | for idx, tt := range tbs { 59 | cli, err := NewRateLimitedClient(tt.qps, tt.burst) 60 | if err != nil { 61 | t.Fatalf("#%d: failed to create a new client (%v)", idx, err) 62 | } 63 | 64 | start := time.Now() 65 | 66 | errc := make(chan error, tt.requests) 67 | for i := 0; i < tt.requests; i++ { 68 | go func() { 69 | var ctx context.Context 70 | if tt.ctxTimeout > 0 { 71 | var cancel context.CancelFunc 72 | ctx, cancel = context.WithTimeout(context.TODO(), tt.ctxTimeout) 73 | defer cancel() 74 | } else { 75 | ctx = context.TODO() 76 | } 77 | req, err := http.NewRequest(http.MethodGet, u, nil) 78 | if err != nil { 79 | errc <- err 80 | return 81 | } 82 | _, err = cli.Do(req.WithContext(ctx)) 83 | errc <- err 84 | }() 85 | } 86 | 87 | failed := false 88 | for i := 0; i < tt.requests; i++ { 89 | err = <-errc 90 | switch { 91 | case tt.err == "": // expects no error 92 | if err != nil { 93 | t.Errorf("#%d-%d: unexpected error %v", idx, i, err) 94 | } 95 | case tt.err != "": // expects error 96 | if err == nil { 97 | // this means that the request did not get throttled. 98 | continue 99 | } 100 | if !strings.Contains(err.Error(), tt.err) && 101 | // TODO: why does this happen even when ctx is not canceled 102 | // ref. https://github.com/golang/go/issues/36848 103 | !strings.Contains(err.Error(), "i/o timeout") { 104 | t.Errorf("#%d-%d: expected %q, got %v", idx, i, tt.err, err) 105 | } 106 | failed = true 107 | } 108 | } 109 | 110 | if tt.err != "" && !failed { 111 | t.Fatalf("#%d: expected failure %q, got no error", idx, tt.err) 112 | } 113 | 114 | if tt.err == "" { 115 | observedDuration := time.Since(start).Round(time.Second) 116 | expectedDuration := time.Duration(0) 117 | if tt.qps+tt.burst < tt.requests { 118 | expectedDuration = (time.Duration(tt.requests/(tt.qps)) * time.Second) 119 | } 120 | if expectedDuration > 0 && observedDuration > expectedDuration { 121 | t.Fatalf("with rate limit, requests expected duration %v, got %v", expectedDuration, observedDuration) 122 | } 123 | } 124 | } 125 | } 126 | 127 | func testHandler(w http.ResponseWriter, req *http.Request) { 128 | switch req.Method { 129 | case "GET": 130 | fmt.Fprint(w, `test`) 131 | default: 132 | http.Error(w, "Method Not Allowed", 405) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/client/client.go: -------------------------------------------------------------------------------- 1 | // Package client implements client-side operations on auth configmap. 2 | package client 3 | 4 | import ( 5 | "context" 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/sirupsen/logrus" 10 | core_v1 "k8s.io/api/core/v1" 11 | k8s_errors "k8s.io/apimachinery/pkg/api/errors" 12 | meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | client_v1 "k8s.io/client-go/kubernetes/typed/core/v1" 14 | "k8s.io/client-go/util/retry" 15 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 16 | "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/configmap" 17 | ) 18 | 19 | // Client defines configmap client methods. 20 | type Client interface { 21 | AddRole(role *config.RoleMapping) (*core_v1.ConfigMap, error) 22 | AddUser(user *config.UserMapping) (*core_v1.ConfigMap, error) 23 | } 24 | 25 | const mapName = "aws-auth" 26 | 27 | // New creates a new "Client". 28 | func New(cli client_v1.ConfigMapInterface) Client { 29 | return &client{ 30 | getMap: func() (*core_v1.ConfigMap, error) { 31 | return cli.Get(context.TODO(), mapName, meta_v1.GetOptions{}) 32 | }, 33 | updateMap: func(m *core_v1.ConfigMap) (cm *core_v1.ConfigMap, err error) { 34 | cm, err = cli.Update(context.TODO(), m, meta_v1.UpdateOptions{}) 35 | return cm, err 36 | }, 37 | } 38 | } 39 | 40 | type client struct { 41 | // define as function types for testing 42 | getMap func() (*core_v1.ConfigMap, error) 43 | updateMap func(m *core_v1.ConfigMap) (cm *core_v1.ConfigMap, err error) 44 | } 45 | 46 | func (cli *client) AddRole(role *config.RoleMapping) (*core_v1.ConfigMap, error) { 47 | if role == nil { 48 | return nil, errors.New("empty role") 49 | } 50 | return cli.add(role, nil) 51 | } 52 | 53 | func (cli *client) AddUser(user *config.UserMapping) (*core_v1.ConfigMap, error) { 54 | if user == nil { 55 | return nil, errors.New("empty user") 56 | } 57 | return cli.add(nil, user) 58 | } 59 | 60 | func (cli *client) add(role *config.RoleMapping, user *config.UserMapping) (cm *core_v1.ConfigMap, err error) { 61 | if role == nil && user == nil { 62 | return nil, errors.New("empty role/user") 63 | } 64 | err = retry.RetryOnConflict(retry.DefaultRetry, func() error { 65 | cm, err = cli.getMap() 66 | if err != nil { 67 | if k8s_errors.IsNotFound(err) { 68 | logrus.WithError(err).Warn("not found map " + mapName) 69 | } 70 | return err 71 | } 72 | 73 | data := cm.Data 74 | 75 | userMappings, roleMappings, awsAccounts, err := configmap.ParseMap(data) 76 | if err != nil { 77 | return fmt.Errorf("failed to parse configmap %v", err) 78 | } 79 | 80 | if role != nil { 81 | err = role.Validate() 82 | if err != nil { 83 | return fmt.Errorf("role is invalid: %v", err) 84 | } 85 | 86 | for _, r := range roleMappings { 87 | if r.Key() == role.Key() { 88 | return fmt.Errorf("cannot add duplicate role ARN %q", role.Key()) 89 | } 90 | } 91 | roleMappings = append(roleMappings, *role) 92 | } 93 | 94 | if user != nil { 95 | err = user.Validate() 96 | if err != nil { 97 | return fmt.Errorf("user is invalid: %v", err) 98 | } 99 | for _, r := range userMappings { 100 | if r.Key() == user.Key() { 101 | return fmt.Errorf("cannot add duplicate user ARN %q", user.Key()) 102 | } 103 | } 104 | userMappings = append(userMappings, *user) 105 | } 106 | 107 | data, err = configmap.EncodeMap(userMappings, roleMappings, awsAccounts) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | cm.Data = data 113 | 114 | updatedCm, err := cli.updateMap(cm) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | cm = updatedCm 120 | return nil 121 | }) 122 | return cm, err 123 | } 124 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/client/client_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | core_v1 "k8s.io/api/core/v1" 9 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 10 | "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/configmap" 11 | ) 12 | 13 | func TestAddUser(t *testing.T) { 14 | cli := makeTestClient(t, 15 | nil, 16 | []config.RoleMapping{ 17 | {RoleARN: "a", Username: "a", Groups: []string{"a"}}, 18 | }, 19 | nil, 20 | ) 21 | newUser := config.UserMapping{UserARN: "a", Username: "a", Groups: []string{"a"}} 22 | cm, err := cli.AddUser(&newUser) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | u, _, _, err := configmap.ParseMap(cm.Data) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | updatedUser := u[0] 31 | if !reflect.DeepEqual(newUser, updatedUser) { 32 | t.Fatalf("unexpected updated user %+v", updatedUser) 33 | } 34 | 35 | if _, err := cli.AddRole(&config.RoleMapping{RoleARN: "a"}); err == nil || !strings.Contains(err.Error(), `cannot add duplicate role ARN`) { 36 | t.Fatal(err) 37 | } 38 | } 39 | 40 | func TestAddRole(t *testing.T) { 41 | cli := makeTestClient(t, 42 | []config.UserMapping{ 43 | {UserARN: "a", Username: "a", Groups: []string{"a"}}, 44 | }, 45 | nil, 46 | nil, 47 | ) 48 | newRole := config.RoleMapping{RoleARN: "a", Username: "a", Groups: []string{"a"}} 49 | cm, err := cli.AddRole(&newRole) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | _, r, _, err := configmap.ParseMap(cm.Data) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | updatedRole := r[0] 58 | if !reflect.DeepEqual(newRole, updatedRole) { 59 | t.Fatalf("unexpected updated role %+v", updatedRole) 60 | } 61 | 62 | if _, err := cli.AddUser(&config.UserMapping{UserARN: "a"}); err == nil || !strings.Contains(err.Error(), `cannot add duplicate user ARN`) { 63 | t.Fatal(err) 64 | } 65 | 66 | cli = makeTestClient(t, 67 | nil, 68 | nil, 69 | nil, 70 | ) 71 | newSSORole := config.RoleMapping{ 72 | RoleARN: "", 73 | SSO: &config.SSOARNMatcher{ 74 | PermissionSetName: "ViewOnlyAccess", 75 | AccountID: "012345678912", 76 | }, 77 | Username: "b", 78 | Groups: []string{"b"}} 79 | cm, err = cli.AddRole(&newSSORole) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | _, srm, _, err := configmap.ParseMap(cm.Data) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | updatedRole = srm[0] 88 | if !reflect.DeepEqual(newSSORole, updatedRole) { 89 | t.Fatalf("unexpected updated role %+v", updatedRole) 90 | } 91 | 92 | cli = makeTestClient(t, 93 | nil, 94 | []config.RoleMapping{newSSORole}, 95 | nil, 96 | ) 97 | if _, err := cli.AddRole(&newSSORole); err == nil || !strings.Contains(err.Error(), `cannot add duplicate role ARN`) { 98 | t.Fatal(err) 99 | } 100 | } 101 | 102 | func makeTestClient( 103 | t *testing.T, 104 | userMappings []config.UserMapping, 105 | roleMappings []config.RoleMapping, 106 | awsAccounts []string, 107 | ) Client { 108 | d, err := configmap.EncodeMap(userMappings, roleMappings, awsAccounts) 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | return &client{ 113 | getMap: func() (*core_v1.ConfigMap, error) { 114 | return &core_v1.ConfigMap{Data: d}, nil 115 | }, 116 | updateMap: func(m *core_v1.ConfigMap) (*core_v1.ConfigMap, error) { 117 | return m, nil 118 | }, 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/mapper.go: -------------------------------------------------------------------------------- 1 | package configmap 2 | 3 | import ( 4 | "strings" 5 | 6 | "sigs.k8s.io/aws-iam-authenticator/pkg/errutil" 7 | "sigs.k8s.io/aws-iam-authenticator/pkg/token" 8 | 9 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 10 | "sigs.k8s.io/aws-iam-authenticator/pkg/mapper" 11 | ) 12 | 13 | type ConfigMapMapper struct { 14 | *MapStore 15 | } 16 | 17 | var _ mapper.Mapper = &ConfigMapMapper{} 18 | 19 | func NewConfigMapMapper(cfg config.Config) (*ConfigMapMapper, error) { 20 | ms, err := New(cfg.Master, cfg.Kubeconfig) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &ConfigMapMapper{ms}, nil 25 | } 26 | 27 | func (m *ConfigMapMapper) Name() string { 28 | return mapper.ModeEKSConfigMap 29 | } 30 | 31 | func (m *ConfigMapMapper) Start(stopCh <-chan struct{}) error { 32 | m.startLoadConfigMap(stopCh) 33 | return nil 34 | } 35 | 36 | func (m *ConfigMapMapper) Map(identity *token.Identity) (*config.IdentityMapping, error) { 37 | canonicalARN := strings.ToLower(identity.CanonicalARN) 38 | 39 | rm, err := m.RoleMapping(canonicalARN) 40 | // TODO: Check for non Role/UserNotFound errors 41 | if err == nil { 42 | return &config.IdentityMapping{ 43 | IdentityARN: canonicalARN, 44 | Username: rm.Username, 45 | Groups: rm.Groups, 46 | }, nil 47 | } 48 | 49 | um, err := m.UserMapping(canonicalARN) 50 | if err == nil { 51 | return &config.IdentityMapping{ 52 | IdentityARN: canonicalARN, 53 | Username: um.Username, 54 | Groups: um.Groups, 55 | }, nil 56 | } 57 | 58 | return nil, errutil.ErrNotMapped 59 | } 60 | 61 | func (m *ConfigMapMapper) IsAccountAllowed(accountID string) bool { 62 | return m.AWSAccount(accountID) 63 | } 64 | 65 | func (m *ConfigMapMapper) UsernamePrefixReserveList() []string { 66 | return []string{} 67 | } 68 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/yaml/aws-auth-crazy-case-keys.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapRoles: | 8 | - rOlEaRn: arn:aws:iam::555555555555:role/devel-worker-nodes-NodeInstanceRole-74RF4UBDUKL6 9 | UsErNaMe: system:node:{{EC2PrivateDNSName}} 10 | gRoUpS: 11 | - system:bootstrappers 12 | - system:nodes 13 | mapUsers: | 14 | - uSeRaRn: arn:aws:iam::555555555555:user/admin 15 | UsErNaMe: admin 16 | gRoUpS: 17 | - system:masters 18 | mapAccounts: | 19 | - 555555555555 20 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/yaml/aws-auth-crazy-case-top-keys.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | MaPrOlEs: | 8 | - rolearn: arn:aws:iam::555555555555:role/devel-worker-nodes-NodeInstanceRole-74RF4UBDUKL6 9 | username: system:node:{{EC2PrivateDNSName}} 10 | groups: 11 | - system:bootstrappers 12 | - system:nodes 13 | mApUsErS: | 14 | - userarn: arn:aws:iam::555555555555:user/admin 15 | username: admin 16 | groups: 17 | - system:masters 18 | MaPrCcOuNtS: | 19 | - 555555555555 20 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/yaml/aws-auth-lower-case-top-keys.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | maproles: | 8 | - rolearn: arn:aws:iam::555555555555:role/devel-worker-nodes-NodeInstanceRole-74RF4UBDUKL6 9 | username: system:node:{{EC2PrivateDNSName}} 10 | groups: 11 | - system:bootstrappers 12 | - system:nodes 13 | mapusers: | 14 | - userarn: arn:aws:iam::555555555555:user/admin 15 | username: admin 16 | groups: 17 | - system:masters 18 | mapaccounts: | 19 | - 555555555555 20 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/yaml/aws-auth-missing-bar.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapRoles: 8 | - rolearn: arn:aws:iam::555555555555:role/devel-worker-nodes-NodeInstanceRole-74RF4UBDUKL6 9 | username: system:node:{{EC2PrivateDNSName}} 10 | groups: 11 | - system:bootstrappers 12 | - system:nodes 13 | mapUsers: | 14 | - userarn: arn:aws:iam::555555555555:user/admin 15 | username: admin 16 | groups: 17 | - system:masters 18 | mapAccounts: | 19 | - 555555555555 20 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/yaml/aws-auth-open-source-case-keys.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapRoles: | 8 | - roleARN: arn:aws:iam::555555555555:role/devel-worker-nodes-NodeInstanceRole-74RF4UBDUKL6 9 | username: system:node:{{EC2PrivateDNSName}} 10 | groups: 11 | - system:bootstrappers 12 | - system:nodes 13 | mapUsers: | 14 | - userARN: arn:aws:iam::555555555555:user/admin 15 | username: admin 16 | groups: 17 | - system:masters 18 | mapAccounts: | 19 | - 555555555555 20 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/yaml/aws-auth-space-out-of-place.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapRoles: | 8 | - rolearn: arn:aws:iam::555555555555:role/devel-worker-nodes-NodeInstanceRole-74RF4UBDUKL6 9 | username: system:node:{{EC2PrivateDNSName}} 10 | groups: 11 | - system:bootstrappers 12 | - system:nodes 13 | mapUsers: | 14 | - userarn: arn:aws:iam::555555555555:user/admin 15 | username: admin 16 | groups: 17 | - system:masters 18 | mapAccounts: | 19 | - 555555555555 20 | -------------------------------------------------------------------------------- /pkg/mapper/configmap/yaml/aws-auth.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapRoles: | 8 | - rolearn: arn:aws:iam::555555555555:role/devel-worker-nodes-NodeInstanceRole-74RF4UBDUKL6 9 | username: system:node:{{EC2PrivateDNSName}} 10 | groups: 11 | - system:bootstrappers 12 | - system:nodes 13 | mapUsers: | 14 | - userarn: arn:aws:iam::555555555555:user/admin 15 | username: admin 16 | groups: 17 | - system:masters 18 | mapAccounts: | 19 | - 555555555555 20 | -------------------------------------------------------------------------------- /pkg/mapper/crd/apis/iamauthenticator/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package iamauthenticator 15 | 16 | const ( 17 | GroupName = "iamauthenticator.k8s.aws" 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/mapper/crd/apis/iamauthenticator/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | // +k8s:deepcopy-gen=package 15 | // +groupName=iamauthenticator.k8s.aws 16 | 17 | // Package v1alpha1 is the v1alpha1 version of the API. 18 | package v1alpha1 19 | -------------------------------------------------------------------------------- /pkg/mapper/crd/apis/iamauthenticator/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | 24 | iamauthenticator "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator" 25 | ) 26 | 27 | // SchemeGroupVersion is group version used to register these objects 28 | var SchemeGroupVersion = schema.GroupVersion{Group: iamauthenticator.GroupName, Version: "v1alpha1"} 29 | 30 | // Kind takes an unqualified kind and returns back a Group qualified GroupKind 31 | func Kind(kind string) schema.GroupKind { 32 | return SchemeGroupVersion.WithKind(kind).GroupKind() 33 | } 34 | 35 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 36 | func Resource(resource string) schema.GroupResource { 37 | return SchemeGroupVersion.WithResource(resource).GroupResource() 38 | } 39 | 40 | var ( 41 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 42 | AddToScheme = SchemeBuilder.AddToScheme 43 | ) 44 | 45 | // Adds the list of known types to Scheme. 46 | func addKnownTypes(scheme *runtime.Scheme) error { 47 | scheme.AddKnownTypes(SchemeGroupVersion, 48 | &IAMIdentityMapping{}, 49 | &IAMIdentityMappingList{}, 50 | ) 51 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/mapper/crd/apis/iamauthenticator/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // +genclient 24 | // +genclient:nonNamespaced 25 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 26 | 27 | // IAMIdentityMapping is a specification for a IAMIdentityMapping resource 28 | type IAMIdentityMapping struct { 29 | metav1.TypeMeta `json:",inline"` 30 | metav1.ObjectMeta `json:"metadata,omitempty"` 31 | 32 | Spec IAMIdentityMappingSpec `json:"spec"` 33 | Status IAMIdentityMappingStatus `json:"status"` 34 | } 35 | 36 | // IAMIdentityMappingSpec is the spec for a IAMIdentityMapping resource 37 | type IAMIdentityMappingSpec struct { 38 | ARN string `json:"arn"` 39 | Username string `json:"username"` 40 | Groups []string `json:"groups"` 41 | } 42 | 43 | // IAMIdentityMappingStatus is the status for a IAMIdentityMapping resource 44 | type IAMIdentityMappingStatus struct { 45 | CanonicalARN string `json:"canonicalARN"` 46 | UserID string `json:"userID"` 47 | } 48 | 49 | // +genclient:nonNamespaced 50 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 51 | 52 | // IAMIdentityMappingList is a list of IAMIdentityMapping resources 53 | type IAMIdentityMappingList struct { 54 | metav1.TypeMeta `json:",inline"` 55 | metav1.ListMeta `json:"metadata"` 56 | 57 | Items []IAMIdentityMapping `json:"items"` 58 | } 59 | -------------------------------------------------------------------------------- /pkg/mapper/crd/apis/iamauthenticator/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | // Code generated by deepcopy-gen. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in *IAMIdentityMapping) DeepCopyInto(out *IAMIdentityMapping) { 29 | *out = *in 30 | out.TypeMeta = in.TypeMeta 31 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 32 | in.Spec.DeepCopyInto(&out.Spec) 33 | out.Status = in.Status 34 | return 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IAMIdentityMapping. 38 | func (in *IAMIdentityMapping) DeepCopy() *IAMIdentityMapping { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(IAMIdentityMapping) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 48 | func (in *IAMIdentityMapping) DeepCopyObject() runtime.Object { 49 | if c := in.DeepCopy(); c != nil { 50 | return c 51 | } 52 | return nil 53 | } 54 | 55 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 56 | func (in *IAMIdentityMappingList) DeepCopyInto(out *IAMIdentityMappingList) { 57 | *out = *in 58 | out.TypeMeta = in.TypeMeta 59 | in.ListMeta.DeepCopyInto(&out.ListMeta) 60 | if in.Items != nil { 61 | in, out := &in.Items, &out.Items 62 | *out = make([]IAMIdentityMapping, len(*in)) 63 | for i := range *in { 64 | (*in)[i].DeepCopyInto(&(*out)[i]) 65 | } 66 | } 67 | return 68 | } 69 | 70 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IAMIdentityMappingList. 71 | func (in *IAMIdentityMappingList) DeepCopy() *IAMIdentityMappingList { 72 | if in == nil { 73 | return nil 74 | } 75 | out := new(IAMIdentityMappingList) 76 | in.DeepCopyInto(out) 77 | return out 78 | } 79 | 80 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 81 | func (in *IAMIdentityMappingList) DeepCopyObject() runtime.Object { 82 | if c := in.DeepCopy(); c != nil { 83 | return c 84 | } 85 | return nil 86 | } 87 | 88 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 89 | func (in *IAMIdentityMappingSpec) DeepCopyInto(out *IAMIdentityMappingSpec) { 90 | *out = *in 91 | if in.Groups != nil { 92 | in, out := &in.Groups, &out.Groups 93 | *out = make([]string, len(*in)) 94 | copy(*out, *in) 95 | } 96 | return 97 | } 98 | 99 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IAMIdentityMappingSpec. 100 | func (in *IAMIdentityMappingSpec) DeepCopy() *IAMIdentityMappingSpec { 101 | if in == nil { 102 | return nil 103 | } 104 | out := new(IAMIdentityMappingSpec) 105 | in.DeepCopyInto(out) 106 | return out 107 | } 108 | 109 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 110 | func (in *IAMIdentityMappingStatus) DeepCopyInto(out *IAMIdentityMappingStatus) { 111 | *out = *in 112 | return 113 | } 114 | 115 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IAMIdentityMappingStatus. 116 | func (in *IAMIdentityMappingStatus) DeepCopy() *IAMIdentityMappingStatus { 117 | if in == nil { 118 | return nil 119 | } 120 | out := new(IAMIdentityMappingStatus) 121 | in.DeepCopyInto(out) 122 | return out 123 | } 124 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package versioned 19 | 20 | import ( 21 | fmt "fmt" 22 | http "net/http" 23 | 24 | discovery "k8s.io/client-go/discovery" 25 | rest "k8s.io/client-go/rest" 26 | flowcontrol "k8s.io/client-go/util/flowcontrol" 27 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1" 28 | ) 29 | 30 | type Interface interface { 31 | Discovery() discovery.DiscoveryInterface 32 | IamauthenticatorV1alpha1() iamauthenticatorv1alpha1.IamauthenticatorV1alpha1Interface 33 | } 34 | 35 | // Clientset contains the clients for groups. 36 | type Clientset struct { 37 | *discovery.DiscoveryClient 38 | iamauthenticatorV1alpha1 *iamauthenticatorv1alpha1.IamauthenticatorV1alpha1Client 39 | } 40 | 41 | // IamauthenticatorV1alpha1 retrieves the IamauthenticatorV1alpha1Client 42 | func (c *Clientset) IamauthenticatorV1alpha1() iamauthenticatorv1alpha1.IamauthenticatorV1alpha1Interface { 43 | return c.iamauthenticatorV1alpha1 44 | } 45 | 46 | // Discovery retrieves the DiscoveryClient 47 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 48 | if c == nil { 49 | return nil 50 | } 51 | return c.DiscoveryClient 52 | } 53 | 54 | // NewForConfig creates a new Clientset for the given config. 55 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 56 | // NewForConfig will generate a rate-limiter in configShallowCopy. 57 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 58 | // where httpClient was generated with rest.HTTPClientFor(c). 59 | func NewForConfig(c *rest.Config) (*Clientset, error) { 60 | configShallowCopy := *c 61 | 62 | if configShallowCopy.UserAgent == "" { 63 | configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() 64 | } 65 | 66 | // share the transport between all clients 67 | httpClient, err := rest.HTTPClientFor(&configShallowCopy) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return NewForConfigAndClient(&configShallowCopy, httpClient) 73 | } 74 | 75 | // NewForConfigAndClient creates a new Clientset for the given config and http client. 76 | // Note the http client provided takes precedence over the configured transport values. 77 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 78 | // NewForConfigAndClient will generate a rate-limiter in configShallowCopy. 79 | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { 80 | configShallowCopy := *c 81 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 82 | if configShallowCopy.Burst <= 0 { 83 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 84 | } 85 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 86 | } 87 | 88 | var cs Clientset 89 | var err error 90 | cs.iamauthenticatorV1alpha1, err = iamauthenticatorv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return &cs, nil 100 | } 101 | 102 | // NewForConfigOrDie creates a new Clientset for the given config and 103 | // panics if there is an error in the config. 104 | func NewForConfigOrDie(c *rest.Config) *Clientset { 105 | cs, err := NewForConfig(c) 106 | if err != nil { 107 | panic(err) 108 | } 109 | return cs 110 | } 111 | 112 | // New creates a new Clientset for the given RESTClient. 113 | func New(c rest.Interface) *Clientset { 114 | var cs Clientset 115 | cs.iamauthenticatorV1alpha1 = iamauthenticatorv1alpha1.New(c) 116 | 117 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 118 | return &cs 119 | } 120 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "k8s.io/apimachinery/pkg/watch" 23 | "k8s.io/client-go/discovery" 24 | fakediscovery "k8s.io/client-go/discovery/fake" 25 | "k8s.io/client-go/testing" 26 | clientset "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned" 27 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1" 28 | fakeiamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1/fake" 29 | ) 30 | 31 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 32 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 33 | // without applying any field management, validations and/or defaults. It shouldn't be considered a replacement 34 | // for a real clientset and is mostly useful in simple unit tests. 35 | // 36 | // DEPRECATED: NewClientset replaces this with support for field management, which significantly improves 37 | // server side apply testing. NewClientset is only available when apply configurations are generated (e.g. 38 | // via --with-applyconfig). 39 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 40 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 41 | for _, obj := range objects { 42 | if err := o.Add(obj); err != nil { 43 | panic(err) 44 | } 45 | } 46 | 47 | cs := &Clientset{tracker: o} 48 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 49 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 50 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 51 | gvr := action.GetResource() 52 | ns := action.GetNamespace() 53 | watch, err := o.Watch(gvr, ns) 54 | if err != nil { 55 | return false, nil, err 56 | } 57 | return true, watch, nil 58 | }) 59 | 60 | return cs 61 | } 62 | 63 | // Clientset implements clientset.Interface. Meant to be embedded into a 64 | // struct to get a default implementation. This makes faking out just the method 65 | // you want to test easier. 66 | type Clientset struct { 67 | testing.Fake 68 | discovery *fakediscovery.FakeDiscovery 69 | tracker testing.ObjectTracker 70 | } 71 | 72 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 73 | return c.discovery 74 | } 75 | 76 | func (c *Clientset) Tracker() testing.ObjectTracker { 77 | return c.tracker 78 | } 79 | 80 | var ( 81 | _ clientset.Interface = &Clientset{} 82 | _ testing.FakeClient = &Clientset{} 83 | ) 84 | 85 | // IamauthenticatorV1alpha1 retrieves the IamauthenticatorV1alpha1Client 86 | func (c *Clientset) IamauthenticatorV1alpha1() iamauthenticatorv1alpha1.IamauthenticatorV1alpha1Interface { 87 | return &fakeiamauthenticatorv1alpha1.FakeIamauthenticatorV1alpha1{Fake: &c.Fake} 88 | } 89 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package has the automatically generated fake clientset. 19 | package fake 20 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | runtime "k8s.io/apimachinery/pkg/runtime" 23 | schema "k8s.io/apimachinery/pkg/runtime/schema" 24 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 25 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 26 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator/v1alpha1" 27 | ) 28 | 29 | var scheme = runtime.NewScheme() 30 | var codecs = serializer.NewCodecFactory(scheme) 31 | 32 | var localSchemeBuilder = runtime.SchemeBuilder{ 33 | iamauthenticatorv1alpha1.AddToScheme, 34 | } 35 | 36 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 37 | // of clientsets, like in: 38 | // 39 | // import ( 40 | // "k8s.io/client-go/kubernetes" 41 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 42 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 43 | // ) 44 | // 45 | // kclientset, _ := kubernetes.NewForConfig(c) 46 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 47 | // 48 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 49 | // correctly. 50 | var AddToScheme = localSchemeBuilder.AddToScheme 51 | 52 | func init() { 53 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 54 | utilruntime.Must(AddToScheme(scheme)) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package contains the scheme of the automatically generated clientset. 19 | package scheme 20 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package scheme 19 | 20 | import ( 21 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | runtime "k8s.io/apimachinery/pkg/runtime" 23 | schema "k8s.io/apimachinery/pkg/runtime/schema" 24 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 25 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 26 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator/v1alpha1" 27 | ) 28 | 29 | var Scheme = runtime.NewScheme() 30 | var Codecs = serializer.NewCodecFactory(Scheme) 31 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 32 | var localSchemeBuilder = runtime.SchemeBuilder{ 33 | iamauthenticatorv1alpha1.AddToScheme, 34 | } 35 | 36 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 37 | // of clientsets, like in: 38 | // 39 | // import ( 40 | // "k8s.io/client-go/kubernetes" 41 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 42 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 43 | // ) 44 | // 45 | // kclientset, _ := kubernetes.NewForConfig(c) 46 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 47 | // 48 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 49 | // correctly. 50 | var AddToScheme = localSchemeBuilder.AddToScheme 51 | 52 | func init() { 53 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 54 | utilruntime.Must(AddToScheme(Scheme)) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package has the automatically generated typed clients. 19 | package v1alpha1 20 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // Package fake has the automatically generated clients. 19 | package fake 20 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1/fake/fake_iamauthenticator_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | rest "k8s.io/client-go/rest" 22 | testing "k8s.io/client-go/testing" 23 | v1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1" 24 | ) 25 | 26 | type FakeIamauthenticatorV1alpha1 struct { 27 | *testing.Fake 28 | } 29 | 30 | func (c *FakeIamauthenticatorV1alpha1) IAMIdentityMappings() v1alpha1.IAMIdentityMappingInterface { 31 | return newFakeIAMIdentityMappings(c) 32 | } 33 | 34 | // RESTClient returns a RESTClient that is used to communicate 35 | // with API server by this client implementation. 36 | func (c *FakeIamauthenticatorV1alpha1) RESTClient() rest.Interface { 37 | var ret *rest.RESTClient 38 | return ret 39 | } 40 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1/fake/fake_iamidentitymapping.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | gentype "k8s.io/client-go/gentype" 22 | v1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator/v1alpha1" 23 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1" 24 | ) 25 | 26 | // fakeIAMIdentityMappings implements IAMIdentityMappingInterface 27 | type fakeIAMIdentityMappings struct { 28 | *gentype.FakeClientWithList[*v1alpha1.IAMIdentityMapping, *v1alpha1.IAMIdentityMappingList] 29 | Fake *FakeIamauthenticatorV1alpha1 30 | } 31 | 32 | func newFakeIAMIdentityMappings(fake *FakeIamauthenticatorV1alpha1) iamauthenticatorv1alpha1.IAMIdentityMappingInterface { 33 | return &fakeIAMIdentityMappings{ 34 | gentype.NewFakeClientWithList[*v1alpha1.IAMIdentityMapping, *v1alpha1.IAMIdentityMappingList]( 35 | fake.Fake, 36 | "", 37 | v1alpha1.SchemeGroupVersion.WithResource("iamidentitymappings"), 38 | v1alpha1.SchemeGroupVersion.WithKind("IAMIdentityMapping"), 39 | func() *v1alpha1.IAMIdentityMapping { return &v1alpha1.IAMIdentityMapping{} }, 40 | func() *v1alpha1.IAMIdentityMappingList { return &v1alpha1.IAMIdentityMappingList{} }, 41 | func(dst, src *v1alpha1.IAMIdentityMappingList) { dst.ListMeta = src.ListMeta }, 42 | func(list *v1alpha1.IAMIdentityMappingList) []*v1alpha1.IAMIdentityMapping { 43 | return gentype.ToPointerSlice(list.Items) 44 | }, 45 | func(list *v1alpha1.IAMIdentityMappingList, items []*v1alpha1.IAMIdentityMapping) { 46 | list.Items = gentype.FromPointerSlice(items) 47 | }, 48 | ), 49 | fake, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | type IAMIdentityMappingExpansion interface{} 21 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1/iamauthenticator_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | http "net/http" 22 | 23 | rest "k8s.io/client-go/rest" 24 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator/v1alpha1" 25 | scheme "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned/scheme" 26 | ) 27 | 28 | type IamauthenticatorV1alpha1Interface interface { 29 | RESTClient() rest.Interface 30 | IAMIdentityMappingsGetter 31 | } 32 | 33 | // IamauthenticatorV1alpha1Client is used to interact with features provided by the iamauthenticator.k8s.aws group. 34 | type IamauthenticatorV1alpha1Client struct { 35 | restClient rest.Interface 36 | } 37 | 38 | func (c *IamauthenticatorV1alpha1Client) IAMIdentityMappings() IAMIdentityMappingInterface { 39 | return newIAMIdentityMappings(c) 40 | } 41 | 42 | // NewForConfig creates a new IamauthenticatorV1alpha1Client for the given config. 43 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 44 | // where httpClient was generated with rest.HTTPClientFor(c). 45 | func NewForConfig(c *rest.Config) (*IamauthenticatorV1alpha1Client, error) { 46 | config := *c 47 | if err := setConfigDefaults(&config); err != nil { 48 | return nil, err 49 | } 50 | httpClient, err := rest.HTTPClientFor(&config) 51 | if err != nil { 52 | return nil, err 53 | } 54 | return NewForConfigAndClient(&config, httpClient) 55 | } 56 | 57 | // NewForConfigAndClient creates a new IamauthenticatorV1alpha1Client for the given config and http client. 58 | // Note the http client provided takes precedence over the configured transport values. 59 | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*IamauthenticatorV1alpha1Client, error) { 60 | config := *c 61 | if err := setConfigDefaults(&config); err != nil { 62 | return nil, err 63 | } 64 | client, err := rest.RESTClientForConfigAndClient(&config, h) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return &IamauthenticatorV1alpha1Client{client}, nil 69 | } 70 | 71 | // NewForConfigOrDie creates a new IamauthenticatorV1alpha1Client for the given config and 72 | // panics if there is an error in the config. 73 | func NewForConfigOrDie(c *rest.Config) *IamauthenticatorV1alpha1Client { 74 | client, err := NewForConfig(c) 75 | if err != nil { 76 | panic(err) 77 | } 78 | return client 79 | } 80 | 81 | // New creates a new IamauthenticatorV1alpha1Client for the given RESTClient. 82 | func New(c rest.Interface) *IamauthenticatorV1alpha1Client { 83 | return &IamauthenticatorV1alpha1Client{c} 84 | } 85 | 86 | func setConfigDefaults(config *rest.Config) error { 87 | gv := iamauthenticatorv1alpha1.SchemeGroupVersion 88 | config.GroupVersion = &gv 89 | config.APIPath = "/apis" 90 | config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() 91 | 92 | if config.UserAgent == "" { 93 | config.UserAgent = rest.DefaultKubernetesUserAgent() 94 | } 95 | 96 | return nil 97 | } 98 | 99 | // RESTClient returns a RESTClient that is used to communicate 100 | // with API server by this client implementation. 101 | func (c *IamauthenticatorV1alpha1Client) RESTClient() rest.Interface { 102 | if c == nil { 103 | return nil 104 | } 105 | return c.restClient 106 | } 107 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/clientset/versioned/typed/iamauthenticator/v1alpha1/iamidentitymapping.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | context "context" 22 | 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | types "k8s.io/apimachinery/pkg/types" 25 | watch "k8s.io/apimachinery/pkg/watch" 26 | gentype "k8s.io/client-go/gentype" 27 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator/v1alpha1" 28 | scheme "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned/scheme" 29 | ) 30 | 31 | // IAMIdentityMappingsGetter has a method to return a IAMIdentityMappingInterface. 32 | // A group's client should implement this interface. 33 | type IAMIdentityMappingsGetter interface { 34 | IAMIdentityMappings() IAMIdentityMappingInterface 35 | } 36 | 37 | // IAMIdentityMappingInterface has methods to work with IAMIdentityMapping resources. 38 | type IAMIdentityMappingInterface interface { 39 | Create(ctx context.Context, iAMIdentityMapping *iamauthenticatorv1alpha1.IAMIdentityMapping, opts v1.CreateOptions) (*iamauthenticatorv1alpha1.IAMIdentityMapping, error) 40 | Update(ctx context.Context, iAMIdentityMapping *iamauthenticatorv1alpha1.IAMIdentityMapping, opts v1.UpdateOptions) (*iamauthenticatorv1alpha1.IAMIdentityMapping, error) 41 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 42 | UpdateStatus(ctx context.Context, iAMIdentityMapping *iamauthenticatorv1alpha1.IAMIdentityMapping, opts v1.UpdateOptions) (*iamauthenticatorv1alpha1.IAMIdentityMapping, error) 43 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error 44 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error 45 | Get(ctx context.Context, name string, opts v1.GetOptions) (*iamauthenticatorv1alpha1.IAMIdentityMapping, error) 46 | List(ctx context.Context, opts v1.ListOptions) (*iamauthenticatorv1alpha1.IAMIdentityMappingList, error) 47 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) 48 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *iamauthenticatorv1alpha1.IAMIdentityMapping, err error) 49 | IAMIdentityMappingExpansion 50 | } 51 | 52 | // iAMIdentityMappings implements IAMIdentityMappingInterface 53 | type iAMIdentityMappings struct { 54 | *gentype.ClientWithList[*iamauthenticatorv1alpha1.IAMIdentityMapping, *iamauthenticatorv1alpha1.IAMIdentityMappingList] 55 | } 56 | 57 | // newIAMIdentityMappings returns a IAMIdentityMappings 58 | func newIAMIdentityMappings(c *IamauthenticatorV1alpha1Client) *iAMIdentityMappings { 59 | return &iAMIdentityMappings{ 60 | gentype.NewClientWithList[*iamauthenticatorv1alpha1.IAMIdentityMapping, *iamauthenticatorv1alpha1.IAMIdentityMappingList]( 61 | "iamidentitymappings", 62 | c.RESTClient(), 63 | scheme.ParameterCodec, 64 | "", 65 | func() *iamauthenticatorv1alpha1.IAMIdentityMapping { 66 | return &iamauthenticatorv1alpha1.IAMIdentityMapping{} 67 | }, 68 | func() *iamauthenticatorv1alpha1.IAMIdentityMappingList { 69 | return &iamauthenticatorv1alpha1.IAMIdentityMappingList{} 70 | }, 71 | ), 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package externalversions 19 | 20 | import ( 21 | fmt "fmt" 22 | 23 | schema "k8s.io/apimachinery/pkg/runtime/schema" 24 | cache "k8s.io/client-go/tools/cache" 25 | v1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator/v1alpha1" 26 | ) 27 | 28 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 29 | // sharedInformers based on type 30 | type GenericInformer interface { 31 | Informer() cache.SharedIndexInformer 32 | Lister() cache.GenericLister 33 | } 34 | 35 | type genericInformer struct { 36 | informer cache.SharedIndexInformer 37 | resource schema.GroupResource 38 | } 39 | 40 | // Informer returns the SharedIndexInformer. 41 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 42 | return f.informer 43 | } 44 | 45 | // Lister returns the GenericLister. 46 | func (f *genericInformer) Lister() cache.GenericLister { 47 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 48 | } 49 | 50 | // ForResource gives generic access to a shared informer of the matching type 51 | // TODO extend this to unknown resources with a client pool 52 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 53 | switch resource { 54 | // Group=iamauthenticator.k8s.aws, Version=v1alpha1 55 | case v1alpha1.SchemeGroupVersion.WithResource("iamidentitymappings"): 56 | return &genericInformer{resource: resource.GroupResource(), informer: f.Iamauthenticator().V1alpha1().IAMIdentityMappings().Informer()}, nil 57 | 58 | } 59 | 60 | return nil, fmt.Errorf("no informer found for %v", resource) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/informers/externalversions/iamauthenticator/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package iamauthenticator 19 | 20 | import ( 21 | v1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/informers/externalversions/iamauthenticator/v1alpha1" 22 | internalinterfaces "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/informers/externalversions/internalinterfaces" 23 | ) 24 | 25 | // Interface provides access to each of this group's versions. 26 | type Interface interface { 27 | // V1alpha1 provides access to shared informers for resources in V1alpha1. 28 | V1alpha1() v1alpha1.Interface 29 | } 30 | 31 | type group struct { 32 | factory internalinterfaces.SharedInformerFactory 33 | namespace string 34 | tweakListOptions internalinterfaces.TweakListOptionsFunc 35 | } 36 | 37 | // New returns a new Interface. 38 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 39 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 40 | } 41 | 42 | // V1alpha1 returns a new v1alpha1.Interface. 43 | func (g *group) V1alpha1() v1alpha1.Interface { 44 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/informers/externalversions/iamauthenticator/v1alpha1/iamidentitymapping.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | context "context" 22 | time "time" 23 | 24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | watch "k8s.io/apimachinery/pkg/watch" 27 | cache "k8s.io/client-go/tools/cache" 28 | apisiamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator/v1alpha1" 29 | versioned "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned" 30 | internalinterfaces "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/informers/externalversions/internalinterfaces" 31 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/listers/iamauthenticator/v1alpha1" 32 | ) 33 | 34 | // IAMIdentityMappingInformer provides access to a shared informer and lister for 35 | // IAMIdentityMappings. 36 | type IAMIdentityMappingInformer interface { 37 | Informer() cache.SharedIndexInformer 38 | Lister() iamauthenticatorv1alpha1.IAMIdentityMappingLister 39 | } 40 | 41 | type iAMIdentityMappingInformer struct { 42 | factory internalinterfaces.SharedInformerFactory 43 | tweakListOptions internalinterfaces.TweakListOptionsFunc 44 | } 45 | 46 | // NewIAMIdentityMappingInformer constructs a new informer for IAMIdentityMapping type. 47 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 48 | // one. This reduces memory footprint and number of connections to the server. 49 | func NewIAMIdentityMappingInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 50 | return NewFilteredIAMIdentityMappingInformer(client, resyncPeriod, indexers, nil) 51 | } 52 | 53 | // NewFilteredIAMIdentityMappingInformer constructs a new informer for IAMIdentityMapping type. 54 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 55 | // one. This reduces memory footprint and number of connections to the server. 56 | func NewFilteredIAMIdentityMappingInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 57 | return cache.NewSharedIndexInformer( 58 | &cache.ListWatch{ 59 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) { 60 | if tweakListOptions != nil { 61 | tweakListOptions(&options) 62 | } 63 | return client.IamauthenticatorV1alpha1().IAMIdentityMappings().List(context.TODO(), options) 64 | }, 65 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { 66 | if tweakListOptions != nil { 67 | tweakListOptions(&options) 68 | } 69 | return client.IamauthenticatorV1alpha1().IAMIdentityMappings().Watch(context.TODO(), options) 70 | }, 71 | }, 72 | &apisiamauthenticatorv1alpha1.IAMIdentityMapping{}, 73 | resyncPeriod, 74 | indexers, 75 | ) 76 | } 77 | 78 | func (f *iAMIdentityMappingInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 79 | return NewFilteredIAMIdentityMappingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 80 | } 81 | 82 | func (f *iAMIdentityMappingInformer) Informer() cache.SharedIndexInformer { 83 | return f.factory.InformerFor(&apisiamauthenticatorv1alpha1.IAMIdentityMapping{}, f.defaultInformer) 84 | } 85 | 86 | func (f *iAMIdentityMappingInformer) Lister() iamauthenticatorv1alpha1.IAMIdentityMappingLister { 87 | return iamauthenticatorv1alpha1.NewIAMIdentityMappingLister(f.Informer().GetIndexer()) 88 | } 89 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/informers/externalversions/iamauthenticator/v1alpha1/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | internalinterfaces "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/informers/externalversions/internalinterfaces" 22 | ) 23 | 24 | // Interface provides access to all the informers in this group version. 25 | type Interface interface { 26 | // IAMIdentityMappings returns a IAMIdentityMappingInformer. 27 | IAMIdentityMappings() IAMIdentityMappingInformer 28 | } 29 | 30 | type version struct { 31 | factory internalinterfaces.SharedInformerFactory 32 | namespace string 33 | tweakListOptions internalinterfaces.TweakListOptionsFunc 34 | } 35 | 36 | // New returns a new Interface. 37 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 38 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 39 | } 40 | 41 | // IAMIdentityMappings returns a IAMIdentityMappingInformer. 42 | func (v *version) IAMIdentityMappings() IAMIdentityMappingInformer { 43 | return &iAMIdentityMappingInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} 44 | } 45 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package internalinterfaces 19 | 20 | import ( 21 | time "time" 22 | 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | cache "k8s.io/client-go/tools/cache" 26 | versioned "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned" 27 | ) 28 | 29 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 30 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 31 | 32 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 33 | type SharedInformerFactory interface { 34 | Start(stopCh <-chan struct{}) 35 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 36 | } 37 | 38 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 39 | type TweakListOptionsFunc func(*v1.ListOptions) 40 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/listers/iamauthenticator/v1alpha1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | // IAMIdentityMappingListerExpansion allows custom methods to be added to 21 | // IAMIdentityMappingLister. 22 | type IAMIdentityMappingListerExpansion interface{} 23 | -------------------------------------------------------------------------------- /pkg/mapper/crd/generated/listers/iamauthenticator/v1alpha1/iamidentitymapping.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | labels "k8s.io/apimachinery/pkg/labels" 22 | listers "k8s.io/client-go/listers" 23 | cache "k8s.io/client-go/tools/cache" 24 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator/v1alpha1" 25 | ) 26 | 27 | // IAMIdentityMappingLister helps list IAMIdentityMappings. 28 | // All objects returned here must be treated as read-only. 29 | type IAMIdentityMappingLister interface { 30 | // List lists all IAMIdentityMappings in the indexer. 31 | // Objects returned here must be treated as read-only. 32 | List(selector labels.Selector) (ret []*iamauthenticatorv1alpha1.IAMIdentityMapping, err error) 33 | // Get retrieves the IAMIdentityMapping from the index for a given name. 34 | // Objects returned here must be treated as read-only. 35 | Get(name string) (*iamauthenticatorv1alpha1.IAMIdentityMapping, error) 36 | IAMIdentityMappingListerExpansion 37 | } 38 | 39 | // iAMIdentityMappingLister implements the IAMIdentityMappingLister interface. 40 | type iAMIdentityMappingLister struct { 41 | listers.ResourceIndexer[*iamauthenticatorv1alpha1.IAMIdentityMapping] 42 | } 43 | 44 | // NewIAMIdentityMappingLister returns a new IAMIdentityMappingLister. 45 | func NewIAMIdentityMappingLister(indexer cache.Indexer) IAMIdentityMappingLister { 46 | return &iAMIdentityMappingLister{listers.New[*iamauthenticatorv1alpha1.IAMIdentityMapping](indexer, iamauthenticatorv1alpha1.Resource("iamidentitymapping"))} 47 | } 48 | -------------------------------------------------------------------------------- /pkg/mapper/crd/mapper.go: -------------------------------------------------------------------------------- 1 | package crd 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "sigs.k8s.io/aws-iam-authenticator/pkg/errutil" 9 | "sigs.k8s.io/aws-iam-authenticator/pkg/token" 10 | 11 | "k8s.io/client-go/kubernetes" 12 | "k8s.io/client-go/rest" 13 | "k8s.io/client-go/tools/cache" 14 | "k8s.io/client-go/tools/clientcmd" 15 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 16 | "sigs.k8s.io/aws-iam-authenticator/pkg/mapper" 17 | iamauthenticatorv1alpha1 "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/apis/iamauthenticator/v1alpha1" 18 | "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/controller" 19 | clientset "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/clientset/versioned" 20 | informers "sigs.k8s.io/aws-iam-authenticator/pkg/mapper/crd/generated/informers/externalversions" 21 | ) 22 | 23 | type CRDMapper struct { 24 | *controller.Controller 25 | // iamInformerFactory is an informer factory that must be Started 26 | iamInformerFactory informers.SharedInformerFactory 27 | // iamMappingsSynced is a function to get if the informers have synced 28 | iamMappingsSynced cache.InformerSynced 29 | // iamMappingsIndex is a custom indexer which allows for indexing on canonical arns 30 | iamMappingsIndex cache.Indexer 31 | } 32 | 33 | var _ mapper.Mapper = &CRDMapper{} 34 | 35 | func NewCRDMapper(cfg config.Config) (*CRDMapper, error) { 36 | var err error 37 | var k8sconfig *rest.Config 38 | var kubeClient kubernetes.Interface 39 | var iamClient clientset.Interface 40 | var iamInformerFactory informers.SharedInformerFactory 41 | 42 | if cfg.Master != "" || cfg.Kubeconfig != "" { 43 | k8sconfig, err = clientcmd.BuildConfigFromFlags(cfg.Master, cfg.Kubeconfig) 44 | } else { 45 | k8sconfig, err = rest.InClusterConfig() 46 | } 47 | if err != nil { 48 | return nil, fmt.Errorf("can't create kubernetes config: %v", err) 49 | } 50 | 51 | kubeClient, err = kubernetes.NewForConfig(k8sconfig) 52 | if err != nil { 53 | return nil, fmt.Errorf("can't create kubernetes client: %v", err) 54 | } 55 | 56 | iamClient, err = clientset.NewForConfig(k8sconfig) 57 | if err != nil { 58 | return nil, fmt.Errorf("can't create authenticator client: %v", err) 59 | } 60 | 61 | iamInformerFactory = informers.NewSharedInformerFactory(iamClient, time.Second*36000) 62 | 63 | iamMappingInformer := iamInformerFactory.Iamauthenticator().V1alpha1().IAMIdentityMappings() 64 | iamMappingsSynced := iamMappingInformer.Informer().HasSynced 65 | iamMappingsIndex := iamMappingInformer.Informer().GetIndexer() 66 | 67 | ctrl := controller.New(kubeClient, iamClient, iamMappingInformer) 68 | 69 | return &CRDMapper{ctrl, iamInformerFactory, iamMappingsSynced, iamMappingsIndex}, nil 70 | } 71 | 72 | func NewCRDMapperWithIndexer(iamMappingsIndex cache.Indexer) *CRDMapper { 73 | return &CRDMapper{iamMappingsIndex: iamMappingsIndex} 74 | } 75 | 76 | func (m *CRDMapper) Name() string { 77 | return mapper.ModeCRD 78 | } 79 | 80 | func (m *CRDMapper) Start(stopCh <-chan struct{}) error { 81 | m.iamInformerFactory.Start(stopCh) 82 | go func() { 83 | // Run starts worker goroutines and blocks 84 | if err := m.Controller.Run(2, stopCh); err != nil { 85 | panic(err) 86 | } 87 | }() 88 | 89 | return nil 90 | } 91 | 92 | func (m *CRDMapper) Map(identity *token.Identity) (*config.IdentityMapping, error) { 93 | canonicalARN := strings.ToLower(identity.CanonicalARN) 94 | 95 | var iamidentity *iamauthenticatorv1alpha1.IAMIdentityMapping 96 | var ok bool 97 | objects, err := m.iamMappingsIndex.ByIndex("canonicalARN", canonicalARN) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | if len(objects) > 0 { 103 | for _, obj := range objects { 104 | iamidentity, ok = obj.(*iamauthenticatorv1alpha1.IAMIdentityMapping) 105 | if ok { 106 | break 107 | } 108 | } 109 | 110 | if iamidentity != nil { 111 | return &config.IdentityMapping{ 112 | IdentityARN: canonicalARN, 113 | Username: iamidentity.Spec.Username, 114 | Groups: iamidentity.Spec.Groups, 115 | }, nil 116 | } 117 | } 118 | 119 | return nil, errutil.ErrNotMapped 120 | } 121 | 122 | func (m *CRDMapper) IsAccountAllowed(accountID string) bool { 123 | return false 124 | } 125 | 126 | func (m *CRDMapper) UsernamePrefixReserveList() []string { 127 | return []string{} 128 | } 129 | -------------------------------------------------------------------------------- /pkg/mapper/dynamicfile/mapper.go: -------------------------------------------------------------------------------- 1 | package dynamicfile 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sirupsen/logrus" 7 | "sigs.k8s.io/aws-iam-authenticator/pkg/arn" 8 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 9 | "sigs.k8s.io/aws-iam-authenticator/pkg/errutil" 10 | "sigs.k8s.io/aws-iam-authenticator/pkg/fileutil" 11 | "sigs.k8s.io/aws-iam-authenticator/pkg/mapper" 12 | "sigs.k8s.io/aws-iam-authenticator/pkg/token" 13 | ) 14 | 15 | type DynamicFileMapper struct { 16 | *DynamicFileMapStore 17 | } 18 | 19 | var _ mapper.Mapper = &DynamicFileMapper{} 20 | 21 | func NewDynamicFileMapper(cfg config.Config) (*DynamicFileMapper, error) { 22 | ms, err := NewDynamicFileMapStore(cfg) 23 | if err != nil { 24 | return nil, err 25 | } 26 | if value, exists := cfg.ReservedPrefixConfig[mapper.ModeDynamicFile]; exists { 27 | ms.usernamePrefixReserveList = value.UsernamePrefixReserveList 28 | } 29 | return &DynamicFileMapper{ms}, nil 30 | } 31 | 32 | func (m *DynamicFileMapper) Name() string { 33 | return mapper.ModeDynamicFile 34 | } 35 | 36 | func (m *DynamicFileMapper) Start(stopCh <-chan struct{}) error { 37 | fileutil.StartLoadDynamicFile(m.filename, m.DynamicFileMapStore, stopCh) 38 | return nil 39 | } 40 | 41 | func (m *DynamicFileMapper) Map(identity *token.Identity) (*config.IdentityMapping, error) { 42 | canonicalARN := strings.ToLower(identity.CanonicalARN) 43 | 44 | key := canonicalARN 45 | if m.userIDStrict { 46 | key = identity.UserID 47 | } 48 | 49 | if roleMapping, err := m.RoleMapping(key); err == nil { 50 | if err := m.match(canonicalARN, roleMapping.RoleARN); err != nil { 51 | return nil, err 52 | } 53 | return roleMapping.IdentityMapping(identity), nil 54 | } 55 | 56 | if userMapping, err := m.UserMapping(key); err == nil { 57 | if err := m.match(canonicalARN, userMapping.UserARN); err != nil { 58 | return nil, err 59 | } 60 | return userMapping.IdentityMapping(identity), nil 61 | 62 | } 63 | return nil, errutil.ErrNotMapped 64 | } 65 | 66 | func (m *DynamicFileMapper) match(canonicalARN string, mappingARN string) error { 67 | if m.userIDStrict { 68 | // If ARN is provided, ARN must be validated along with UserID. This avoids having to 69 | // support IAM user name/ARN changes. Without preventing this the mapping would look 70 | // invalid but still work and auditing would be difficult/impossible. 71 | strippedArn, _ := arn.StripPath(mappingARN) 72 | logrus.Infof("additional arn comparison for IAM arn. arn from STS response is %s, arn in mapper is %s", 73 | canonicalARN, strings.ToLower(strippedArn)) 74 | if strippedArn != "" && canonicalARN != strings.ToLower(strippedArn) { 75 | return errutil.ErrIDAndARNMismatch 76 | } 77 | return nil 78 | } 79 | return nil 80 | } 81 | 82 | func (m *DynamicFileMapper) IsAccountAllowed(accountID string) bool { 83 | return m.AWSAccount(accountID) 84 | } 85 | 86 | func (m *DynamicFileMapper) UsernamePrefixReserveList() []string { 87 | return m.usernamePrefixReserveList 88 | } 89 | -------------------------------------------------------------------------------- /pkg/mapper/file/mapper.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "sigs.k8s.io/aws-iam-authenticator/pkg/errutil" 8 | "sigs.k8s.io/aws-iam-authenticator/pkg/token" 9 | 10 | "sigs.k8s.io/aws-iam-authenticator/pkg/arn" 11 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 12 | "sigs.k8s.io/aws-iam-authenticator/pkg/mapper" 13 | ) 14 | 15 | type FileMapper struct { 16 | roleMap map[string]config.RoleMapping 17 | userMap map[string]config.UserMapping 18 | accountMap map[string]bool 19 | usernamePrefixReserveList []string 20 | } 21 | 22 | var _ mapper.Mapper = &FileMapper{} 23 | 24 | func NewFileMapper(cfg config.Config) (*FileMapper, error) { 25 | fileMapper := &FileMapper{ 26 | roleMap: make(map[string]config.RoleMapping), 27 | userMap: make(map[string]config.UserMapping), 28 | accountMap: make(map[string]bool), 29 | } 30 | 31 | for _, m := range cfg.RoleMappings { 32 | err := m.Validate() 33 | if err != nil { 34 | return nil, err 35 | } 36 | if m.RoleARN != "" { 37 | _, canonicalizedARN, err := arn.Canonicalize(m.RoleARN) 38 | if err != nil { 39 | return nil, err 40 | } 41 | m.RoleARN = canonicalizedARN 42 | } 43 | fileMapper.roleMap[m.Key()] = m 44 | } 45 | for _, m := range cfg.UserMappings { 46 | err := m.Validate() 47 | if err != nil { 48 | return nil, err 49 | } 50 | var key string 51 | if m.UserARN != "" { 52 | _, canonicalizedARN, err := arn.Canonicalize(strings.ToLower(m.UserARN)) 53 | if err != nil { 54 | return nil, fmt.Errorf("error canonicalizing ARN: %v", err) 55 | } 56 | key = canonicalizedARN 57 | } 58 | fileMapper.userMap[key] = m 59 | } 60 | for _, m := range cfg.AutoMappedAWSAccounts { 61 | fileMapper.accountMap[m] = true 62 | } 63 | if value, exists := cfg.ReservedPrefixConfig[mapper.ModeMountedFile]; exists { 64 | fileMapper.usernamePrefixReserveList = value.UsernamePrefixReserveList 65 | } 66 | return fileMapper, nil 67 | } 68 | 69 | func NewFileMapperWithMaps( 70 | lowercaseRoleMap map[string]config.RoleMapping, 71 | lowercaseUserMap map[string]config.UserMapping, 72 | accountMap map[string]bool) *FileMapper { 73 | return &FileMapper{ 74 | roleMap: lowercaseRoleMap, 75 | userMap: lowercaseUserMap, 76 | accountMap: accountMap, 77 | } 78 | } 79 | 80 | func (m *FileMapper) Name() string { 81 | return mapper.ModeMountedFile 82 | } 83 | 84 | func (m *FileMapper) Start(_ <-chan struct{}) error { 85 | return nil 86 | } 87 | 88 | func (m *FileMapper) Map(identity *token.Identity) (*config.IdentityMapping, error) { 89 | canonicalARN := strings.ToLower(identity.CanonicalARN) 90 | for _, roleMapping := range m.roleMap { 91 | if roleMapping.Matches(canonicalARN) { 92 | return &config.IdentityMapping{ 93 | IdentityARN: canonicalARN, 94 | Username: roleMapping.Username, 95 | Groups: roleMapping.Groups, 96 | }, nil 97 | } 98 | } 99 | if userMapping, exists := m.userMap[canonicalARN]; exists { 100 | return &config.IdentityMapping{ 101 | IdentityARN: canonicalARN, 102 | Username: userMapping.Username, 103 | Groups: userMapping.Groups, 104 | }, nil 105 | } 106 | return nil, errutil.ErrNotMapped 107 | } 108 | 109 | func (m *FileMapper) IsAccountAllowed(accountID string) bool { 110 | return m.accountMap[accountID] 111 | } 112 | 113 | func (m *FileMapper) UsernamePrefixReserveList() []string { 114 | return m.usernamePrefixReserveList 115 | } 116 | -------------------------------------------------------------------------------- /pkg/mapper/file/mapper_test.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "reflect" 5 | "sigs.k8s.io/aws-iam-authenticator/pkg/token" 6 | "testing" 7 | 8 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 9 | ) 10 | 11 | func init() { 12 | config.SSORoleMatchEnabled = true 13 | } 14 | 15 | func newConfig() config.Config { 16 | return config.Config{ 17 | RoleMappings: []config.RoleMapping{ 18 | { 19 | RoleARN: "arn:aws:iam::012345678910:role/test-role", 20 | Username: "shreyas", 21 | Groups: []string{"system:masters"}, 22 | }, 23 | { 24 | SSO: &config.SSOARNMatcher{ 25 | PermissionSetName: "CookieCutterPermissions", 26 | AccountID: "012345678910", 27 | }, 28 | Username: "cookie-cutter", 29 | Groups: []string{"system:masters"}, 30 | }, 31 | { 32 | // test compatibility with eks 33 | RoleARN: "arn:aws:sts::012345678910:assumed-role/test-assumed-role/session-name", 34 | Username: "test", 35 | Groups: []string{"system:masters"}, 36 | }, 37 | }, 38 | UserMappings: []config.UserMapping{ 39 | { 40 | UserARN: "arn:aws:iam::012345678910:user/donald", 41 | Username: "donald", 42 | Groups: []string{"system:masters"}, 43 | }, 44 | }, 45 | AutoMappedAWSAccounts: []string{"000000000000"}, 46 | } 47 | } 48 | 49 | func TestNewFileMapper(t *testing.T) { 50 | cfg := newConfig() 51 | 52 | expected := &FileMapper{ 53 | roleMap: map[string]config.RoleMapping{ 54 | "arn:aws:iam::012345678910:role/test-role": { 55 | RoleARN: "arn:aws:iam::012345678910:role/test-role", 56 | Username: "shreyas", 57 | Groups: []string{"system:masters"}, 58 | }, 59 | "arn:aws:iam::012345678910:role/awsreservedsso_cookiecutterpermissions_*": { 60 | SSO: &config.SSOARNMatcher{ 61 | PermissionSetName: "CookieCutterPermissions", 62 | AccountID: "012345678910", 63 | }, 64 | Username: "cookie-cutter", 65 | Groups: []string{"system:masters"}, 66 | }, 67 | "arn:aws:iam::012345678910:role/test-assumed-role": { 68 | RoleARN: "arn:aws:iam::012345678910:role/test-assumed-role", 69 | Username: "test", 70 | Groups: []string{"system:masters"}, 71 | }, 72 | }, 73 | userMap: map[string]config.UserMapping{ 74 | "arn:aws:iam::012345678910:user/donald": { 75 | UserARN: "arn:aws:iam::012345678910:user/donald", 76 | Username: "donald", 77 | Groups: []string{"system:masters"}, 78 | }, 79 | }, 80 | accountMap: map[string]bool{ 81 | "000000000000": true, 82 | }, 83 | } 84 | 85 | actual, err := NewFileMapper(cfg) 86 | if err != nil { 87 | t.Errorf("Could not build FileMapper from test config: %v", err) 88 | } 89 | 90 | if !reflect.DeepEqual(actual, expected) { 91 | t.Errorf("FileMapper does not match expected value.\nActual: %v\nExpected: %v", actual, expected) 92 | } 93 | } 94 | 95 | func TestMap(t *testing.T) { 96 | fm, err := NewFileMapper(newConfig()) 97 | if err != nil { 98 | t.Errorf("Could not build FileMapper from test config: %v", err) 99 | } 100 | 101 | identityArn := "arn:aws:iam::012345678910:role/test-role" 102 | identity := token.Identity{ 103 | CanonicalARN: identityArn, 104 | } 105 | expected := &config.IdentityMapping{ 106 | IdentityARN: identityArn, 107 | Username: "shreyas", 108 | Groups: []string{"system:masters"}, 109 | } 110 | actual, err := fm.Map(&identity) 111 | if err != nil { 112 | t.Errorf("Could not map %s: %s", identityArn, err) 113 | } 114 | if !reflect.DeepEqual(actual, expected) { 115 | t.Errorf("FileMapper.Map() does not match expected value for roleMapping:\nActual: %v\nExpected: %v", actual, expected) 116 | } 117 | 118 | identityArn = "arn:aws:iam::012345678910:role/awsreservedsso_cookiecutterpermissions_123123123" 119 | identity = token.Identity{ 120 | CanonicalARN: identityArn, 121 | } 122 | expected = &config.IdentityMapping{ 123 | IdentityARN: identityArn, 124 | Username: "cookie-cutter", 125 | Groups: []string{"system:masters"}, 126 | } 127 | actual, err = fm.Map(&identity) 128 | if err != nil { 129 | t.Errorf("Could not map %s: %s", identityArn, err) 130 | } 131 | if !reflect.DeepEqual(actual, expected) { 132 | t.Errorf("FileMapper.Map() does not match expected value for roleArnLikeMapping:\nActual: %v\nExpected: %v", actual, expected) 133 | } 134 | 135 | identityArn = "arn:aws:iam::012345678910:user/donald" 136 | identity = token.Identity{ 137 | CanonicalARN: identityArn, 138 | } 139 | expected = &config.IdentityMapping{ 140 | IdentityARN: identityArn, 141 | Username: "donald", 142 | Groups: []string{"system:masters"}, 143 | } 144 | actual, err = fm.Map(&identity) 145 | if err != nil { 146 | t.Errorf("Could not map %s: %s", identityArn, err) 147 | } 148 | if !reflect.DeepEqual(actual, expected) { 149 | t.Errorf("FileMapper.Map() does not match expected value for userMapping:\nActual: %v\nExpected: %v", actual, expected) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /pkg/mapper/mapper.go: -------------------------------------------------------------------------------- 1 | package mapper 2 | 3 | import ( 4 | "fmt" 5 | 6 | "sigs.k8s.io/aws-iam-authenticator/pkg/token" 7 | 8 | "github.com/sirupsen/logrus" 9 | "k8s.io/apimachinery/pkg/util/sets" 10 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 11 | ) 12 | 13 | const ( 14 | // Deprecated: use ModeMountedFile instead 15 | ModeFile string = "File" 16 | // Deprecated: use ModeEKSConfigMap instead 17 | ModeConfigMap string = "ConfigMap" 18 | 19 | ModeMountedFile string = "MountedFile" 20 | 21 | ModeEKSConfigMap string = "EKSConfigMap" 22 | 23 | ModeCRD string = "CRD" 24 | 25 | ModeDynamicFile string = "DynamicFile" 26 | ) 27 | 28 | var ( 29 | ValidBackendModeChoices = []string{ModeFile, ModeConfigMap, ModeMountedFile, ModeEKSConfigMap, ModeCRD, ModeDynamicFile} 30 | DeprecatedBackendModeChoices = map[string]string{ 31 | ModeFile: ModeMountedFile, 32 | ModeConfigMap: ModeEKSConfigMap, 33 | } 34 | BackendModeChoices = []string{ModeMountedFile, ModeEKSConfigMap, ModeCRD, ModeDynamicFile} 35 | ) 36 | 37 | type Mapper interface { 38 | Name() string 39 | // Start must be non-blocking 40 | Start(stopCh <-chan struct{}) error 41 | Map(identity *token.Identity) (*config.IdentityMapping, error) 42 | IsAccountAllowed(accountID string) bool 43 | UsernamePrefixReserveList() []string 44 | } 45 | 46 | func ValidateBackendMode(modes []string) []error { 47 | var errs []error 48 | 49 | validModes := sets.NewString(ValidBackendModeChoices...) 50 | for _, mode := range modes { 51 | if !validModes.Has(mode) { 52 | errs = append(errs, fmt.Errorf("backend-mode %q is not a valid mode", mode)) 53 | } 54 | } 55 | 56 | for _, mode := range modes { 57 | if replacementMode, ok := DeprecatedBackendModeChoices[mode]; ok { 58 | logrus.Warningf("warning: backend-mode %q is deprecated, use %q instead", mode, replacementMode) 59 | } 60 | } 61 | 62 | if len(modes) != sets.NewString(modes...).Len() { 63 | errs = append(errs, fmt.Errorf("backend-mode %q has duplicates", modes)) 64 | } 65 | 66 | if len(modes) == 0 { 67 | errs = append(errs, fmt.Errorf("at least one backend-mode must be specified")) 68 | } 69 | 70 | return errs 71 | } 72 | -------------------------------------------------------------------------------- /pkg/mapper/mapper_test.go: -------------------------------------------------------------------------------- 1 | package mapper 2 | 3 | import ( 4 | "testing" 5 | 6 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 7 | ) 8 | 9 | func TestValidateBackendMode(t *testing.T) { 10 | cases := []struct { 11 | name string 12 | cfg config.Config 13 | wantErrs bool 14 | }{ 15 | { 16 | name: "valid backend mode", 17 | cfg: config.Config{ 18 | BackendMode: []string{ModeMountedFile, ModeEKSConfigMap, ModeCRD}, 19 | }, 20 | }, 21 | { 22 | name: "valid deprecated backend mode", 23 | cfg: config.Config{ 24 | BackendMode: []string{ModeFile, ModeConfigMap}, 25 | }, 26 | }, 27 | { 28 | name: "invalid backend mode", 29 | cfg: config.Config{ 30 | BackendMode: []string{"ModeFoo"}, 31 | }, 32 | wantErrs: true, 33 | }, 34 | { 35 | name: "empty backend mode", 36 | cfg: config.Config{ 37 | BackendMode: []string{}, 38 | }, 39 | wantErrs: true, 40 | }, 41 | { 42 | name: "duplicate backend mode", 43 | cfg: config.Config{ 44 | BackendMode: []string{ModeMountedFile, ModeMountedFile}, 45 | }, 46 | wantErrs: true, 47 | }, 48 | } 49 | for _, c := range cases { 50 | t.Run(c.name, func(t *testing.T) { 51 | errs := ValidateBackendMode(c.cfg.BackendMode) 52 | if len(errs) > 0 && !c.wantErrs { 53 | t.Errorf("wanted no errors but got: %v", errs) 54 | } else if len(errs) == 0 && c.wantErrs { 55 | t.Errorf("wanted errors but got none") 56 | } 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "github.com/prometheus/client_golang/prometheus/promauto" 6 | ) 7 | 8 | const ( 9 | Namespace = "aws_iam_authenticator" 10 | Malformed = "malformed_request" 11 | Invalid = "invalid_token" 12 | STSError = "sts_error" 13 | STSThrottling = "sts_throttling" 14 | Unknown = "uknown_user" 15 | Success = "success" 16 | ) 17 | 18 | var authenticatorMetrics Metrics 19 | var initialized bool 20 | 21 | func InitMetrics(registerer prometheus.Registerer) { 22 | authenticatorMetrics = createMetrics(registerer) 23 | initialized = true 24 | } 25 | 26 | // Initialized returns true if InitMetrics() has been called, and false 27 | // otherwise. 28 | func Initialized() bool { 29 | return initialized 30 | } 31 | 32 | func Get() Metrics { 33 | return authenticatorMetrics 34 | } 35 | 36 | // Metrics are handles to the collectors for prometheus for the various metrics we are tracking. 37 | type Metrics struct { 38 | ConfigMapWatchFailures prometheus.Counter 39 | Latency *prometheus.HistogramVec 40 | EC2DescribeInstanceCallCount prometheus.Counter 41 | StsConnectionFailure *prometheus.CounterVec 42 | StsResponses *prometheus.CounterVec 43 | DynamicFileFailures prometheus.Counter 44 | StsThrottling *prometheus.CounterVec 45 | E2ELatency *prometheus.HistogramVec 46 | DynamicFileEnabled prometheus.Gauge 47 | DynamicFileOnly prometheus.Gauge 48 | } 49 | 50 | func createMetrics(reg prometheus.Registerer) Metrics { 51 | factory := promauto.With(reg) 52 | 53 | return Metrics{ 54 | ConfigMapWatchFailures: factory.NewCounter( 55 | prometheus.CounterOpts{ 56 | Namespace: Namespace, 57 | Name: "configmap_watch_failures_total", 58 | Help: "EKS Configmap watch failures", 59 | }, 60 | ), 61 | DynamicFileFailures: factory.NewCounter( 62 | prometheus.CounterOpts{ 63 | Namespace: Namespace, 64 | Name: "dynamicfile_failures_total", 65 | Help: "Dynamic file failures", 66 | }, 67 | ), 68 | StsConnectionFailure: factory.NewCounterVec( 69 | prometheus.CounterOpts{ 70 | Namespace: Namespace, 71 | Name: "sts_connection_failures_total", 72 | Help: "Sts call could not succeed or timedout", 73 | }, []string{"StsRegion"}, 74 | ), 75 | StsThrottling: factory.NewCounterVec( 76 | prometheus.CounterOpts{ 77 | Namespace: Namespace, 78 | Name: "sts_throttling_total", 79 | Help: "Sts call got throttled", 80 | }, []string{"StsRegion"}, 81 | ), 82 | StsResponses: factory.NewCounterVec( 83 | prometheus.CounterOpts{ 84 | Namespace: Namespace, 85 | Name: "sts_responses_total", 86 | Help: "Sts responses with error code label", 87 | }, []string{"ResponseCode", "StsRegion"}, 88 | ), 89 | Latency: factory.NewHistogramVec( 90 | prometheus.HistogramOpts{ 91 | Namespace: Namespace, 92 | Name: "authenticate_latency_seconds", 93 | Help: "Authenticate call latency", 94 | }, 95 | []string{"result"}, 96 | ), 97 | EC2DescribeInstanceCallCount: factory.NewCounter( 98 | prometheus.CounterOpts{ 99 | Namespace: Namespace, 100 | Name: "ec2_describe_instances_calls_total", 101 | Help: "Number of EC2 describe instances calls.", 102 | }, 103 | ), 104 | E2ELatency: factory.NewHistogramVec( 105 | prometheus.HistogramOpts{ 106 | Name: "dynamic_e2e_latency_seconds", 107 | Namespace: Namespace, 108 | Help: "End to end latency in seconds partitioned by type.", 109 | Buckets: []float64{1, 3, 5, 10, 15, 20, 30, 60}, 110 | }, 111 | []string{"type"}, 112 | ), 113 | DynamicFileEnabled: factory.NewGauge( 114 | prometheus.GaugeOpts{ 115 | Name: "dynamic_file_enabled", 116 | Namespace: Namespace, 117 | Help: "Dynamic file in backend mode is enabled", 118 | }, 119 | ), 120 | DynamicFileOnly: factory.NewGauge( 121 | prometheus.GaugeOpts{ 122 | Name: "dynamic_file_only", 123 | Namespace: Namespace, 124 | Help: "Only dynamic file in backend mode is enabled", 125 | }, 126 | ), 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pkg/server/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 by the contributors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package server 18 | 19 | import ( 20 | "net" 21 | "net/http" 22 | 23 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 24 | "sigs.k8s.io/aws-iam-authenticator/pkg/mapper" 25 | ) 26 | 27 | // Server for the authentication webhook. 28 | type Server struct { 29 | // Config is the whole configuration of aws-iam-authenticator used for valid keys and certs, kubeconfig, and so on 30 | config.Config 31 | httpServer http.Server 32 | listener net.Listener 33 | internalHandler *handler 34 | } 35 | 36 | type BackendMapper struct { 37 | mappers []mapper.Mapper 38 | mapperStopCh chan struct{} 39 | currentModes string 40 | } 41 | 42 | // AccessConfig represents the configuration format for cluster access config via backend mode. 43 | type BackendModeConfig struct { 44 | // Time that the object takes from update time to load time 45 | LastUpdatedDateTime string `json:"LastUpdatedDateTime"` 46 | // Version is the version number of the update 47 | Version string `json:"Version"` 48 | BackendMode string `json:"backendMode"` 49 | } 50 | -------------------------------------------------------------------------------- /pkg/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 by the contributors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package pkg 18 | 19 | var ( 20 | // Set at build time 21 | // Semantic version of the build. 22 | Version = "unversioned" 23 | // Commit id of the build. 24 | CommitID = "" 25 | // Build Date of the build. 26 | BuildDate = "" 27 | ) 28 | -------------------------------------------------------------------------------- /tests/e2e/apiserver_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package e2e 15 | 16 | import ( 17 | "context" 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | "time" 22 | 23 | "bytes" 24 | 25 | yamlutil "k8s.io/apimachinery/pkg/util/yaml" 26 | 27 | . "github.com/onsi/ginkgo/v2" 28 | . "github.com/onsi/gomega" 29 | batchv1 "k8s.io/api/batch/v1" 30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 | "k8s.io/apimachinery/pkg/util/wait" 32 | "k8s.io/kubernetes/test/e2e/framework" 33 | ) 34 | 35 | const ( 36 | restartDelay = 3 * time.Second 37 | restartWait = 60 * time.Second 38 | testTimeout = 300 * time.Second 39 | ) 40 | 41 | var SIGDescribe = framework.SIGDescribe("api-machinery") 42 | 43 | var _ = SIGDescribe("apiserver", framework.WithDisruptive(), func() { 44 | f := framework.NewDefaultFramework("apiserver") 45 | 46 | When("the manifest changes", func() { 47 | BeforeEach(func() { 48 | jobPath := filepath.Join(os.Getenv("BASE_DIR"), "apiserver-restart.yaml") 49 | 50 | b, _ := os.ReadFile(jobPath) 51 | decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(b), 100) 52 | jobSpec := &batchv1.Job{} 53 | _ = decoder.Decode(&jobSpec) 54 | 55 | _, _ = f.ClientSet.BatchV1(). 56 | Jobs(kubeSystemNs). 57 | Create(context.TODO(), jobSpec, metav1.CreateOptions{}) 58 | 59 | fmt.Printf("Waiting for apiserver to go down...\n") 60 | err := wait.PollImmediate(restartDelay, restartWait, func() (bool, error) { 61 | _, pingErr := f.ClientSet.CoreV1(). 62 | Nodes(). 63 | List(context.TODO(), metav1.ListOptions{}) 64 | 65 | if pingErr == nil { 66 | return false, nil 67 | } else { 68 | return true, nil 69 | } 70 | }) 71 | 72 | if err != nil { 73 | Fail("Apiserver did not go down! Check if the job was applied correctly?") 74 | } 75 | }) 76 | 77 | AfterEach(func() { 78 | f.ClientSet.BatchV1().Jobs(kubeSystemNs).Delete(context.TODO(), "apiserver-restarter", metav1.DeleteOptions{}) 79 | }) 80 | 81 | It("restarts successfully", func() { 82 | startTime := time.Now() 83 | err := wait.PollImmediate(1, testTimeout, func() (bool, error) { 84 | res, pingErr := f.ClientSet.CoreV1(). 85 | Nodes(). 86 | List(context.TODO(), metav1.ListOptions{}) 87 | 88 | if pingErr == nil { 89 | fmt.Printf("after %ds: apiserver back up: %v nodes\n", int(time.Since(startTime).Seconds()), len(res.Items)) 90 | return true, nil 91 | } else { 92 | fmt.Printf("after %ds: %v\n", int(time.Since(startTime).Seconds()), pingErr) 93 | return false, nil 94 | } 95 | }) 96 | 97 | Expect(err).ToNot(HaveOccurred()) 98 | }) 99 | }) 100 | }) 101 | -------------------------------------------------------------------------------- /tests/e2e/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package e2e 15 | 16 | import ( 17 | "flag" 18 | "fmt" 19 | "log" 20 | "math/rand" 21 | "os" 22 | "path" 23 | "path/filepath" 24 | "testing" 25 | "time" 26 | 27 | "github.com/onsi/ginkgo/v2/reporters" 28 | 29 | . "github.com/onsi/ginkgo/v2" 30 | . "github.com/onsi/gomega" 31 | 32 | "k8s.io/apimachinery/pkg/util/uuid" 33 | "k8s.io/kubernetes/test/e2e/framework" 34 | frameworkconfig "k8s.io/kubernetes/test/e2e/framework/config" 35 | ) 36 | 37 | const kubeconfigEnvVar = "KUBECONFIG" 38 | 39 | func init() { 40 | rand.Seed(time.Now().UTC().UnixNano()) 41 | testing.Init() 42 | // k8s.io/kubernetes/test/e2e/framework requires env KUBECONFIG to be set 43 | // it does not fall back to defaults 44 | if os.Getenv(kubeconfigEnvVar) == "" { 45 | kubeconfig := filepath.Join(os.Getenv("HOME"), ".kube", "config") 46 | os.Setenv(kubeconfigEnvVar, kubeconfig) 47 | } 48 | framework.AfterReadingAllFlags(&framework.TestContext) 49 | 50 | frameworkconfig.CopyFlags(frameworkconfig.Flags, flag.CommandLine) 51 | framework.RegisterCommonFlags(flag.CommandLine) 52 | framework.RegisterClusterFlags(flag.CommandLine) 53 | flag.Parse() 54 | } 55 | 56 | func TestE2E(t *testing.T) { 57 | RegisterFailHandler(Fail) 58 | 59 | // Run tests through the Ginkgo runner with output to console + JUnit for Jenkins 60 | var r []Reporter 61 | if framework.TestContext.ReportDir != "" { 62 | if err := os.MkdirAll(framework.TestContext.ReportDir, 0755); err != nil { 63 | log.Fatalf("Failed creating report directory: %v", err) 64 | } else { 65 | r = append(r, reporters.NewJUnitReporter(path.Join(framework.TestContext.ReportDir, fmt.Sprintf("junit_%v%02d.xml", framework.TestContext.ReportPrefix, GinkgoParallelProcess())))) 66 | } 67 | } 68 | log.Printf("Starting e2e run %q on Ginkgo node %d", uuid.NewUUID(), GinkgoParallelProcess()) // TODO use framework.RunID like upstream 69 | 70 | RunSpecsWithDefaultAndCustomReporters(t, "AWS IAM Authenticator End-to-End Tests", r) 71 | } 72 | -------------------------------------------------------------------------------- /tests/integration/server/main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package server 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "os" 23 | "testing" 24 | _ "unsafe" 25 | ) 26 | 27 | var ( 28 | authenticatorBinaryPath string 29 | roleARN string 30 | testArtifactsDir string 31 | ) 32 | 33 | //go:linkname startEtcd k8s.io/kubernetes/test/integration/framework.startEtcd 34 | func startEtcd() (func(), error) 35 | 36 | func TestMain(m *testing.M) { 37 | flag.StringVar(&authenticatorBinaryPath, "authenticator-binary-path", "/usr/local/bin/aws-iam-authenticator", "Location of the aws-iam-authenticator binary to test with.") 38 | flag.StringVar(&roleARN, "role-arn", "", "ARN of role to be authenticated in the test. This role ARN is added to the configmap and it should be assumable by the test run.") 39 | flag.StringVar(&testArtifactsDir, "test-artifacts-dir", "", "Directory used for artifacts generated from test runs.") 40 | 41 | // TODO(dims): earlier we had `framework.EtcdMain` here. EtcdMain has code to check for number of goroutines leaked 42 | // which trips us up. So using the hack to call `startEtcd` instead which is in the same module. The alternative was 43 | // to use `RunCustomEtcd`, but that has a bug which is being fixed in upstream https://github.com/kubernetes/kubernetes/pull/115254 44 | // once that gets available in a new release in kubernetes, we should switch to it. 45 | flag.Parse() 46 | stop, err := startEtcd() 47 | if err != nil { 48 | fmt.Printf("unable to start etcd: %v\n", err) 49 | os.Exit(1) 50 | } 51 | result := m.Run() 52 | stop() // Don't defer this. See os.Exit documentation. 53 | os.Exit(result) 54 | } 55 | -------------------------------------------------------------------------------- /tests/integration/server/server_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | corev1 "k8s.io/api/core/v1" 9 | rbacv1 "k8s.io/api/rbac/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "sigs.k8s.io/aws-iam-authenticator/pkg/config" 12 | "sigs.k8s.io/aws-iam-authenticator/tests/integration/testutils" 13 | ) 14 | 15 | func TestServer(t *testing.T) { 16 | adminClient, execClient, tearDownFn := testutils.StartAuthenticatorTestFramework( 17 | t, testutils.AuthenticatorTestFrameworkSetup{ 18 | ModifyAuthenticatorServerConfig: func(*config.Config) {}, 19 | AuthenticatorClientBinaryPath: authenticatorBinaryPath, 20 | TestArtifacts: testArtifactsDir, 21 | ClusterID: "test-cluster", 22 | BackendMode: []string{"EKSConfigMap"}, 23 | RoleArn: roleARN, 24 | }, 25 | ) 26 | defer tearDownFn() 27 | 28 | t.Log("Creating aws-auth") 29 | userName := "test-user" 30 | _, err := adminClient.CoreV1().ConfigMaps("kube-system").Create(context.TODO(), &corev1.ConfigMap{ 31 | ObjectMeta: metav1.ObjectMeta{Name: "aws-auth"}, 32 | Data: map[string]string{"mapRoles": fmt.Sprintf(" - rolearn: %s\n username: %s\n", roleARN, userName)}, 33 | }, metav1.CreateOptions{}) 34 | if err != nil { 35 | t.Fatalf("error creating aws-auth configmap: %v\n", err) 36 | } 37 | 38 | _, err = adminClient.RbacV1().ClusterRoleBindings().Create(context.TODO(), &rbacv1.ClusterRoleBinding{ 39 | ObjectMeta: metav1.ObjectMeta{Name: "test-user-binding"}, 40 | Subjects: []rbacv1.Subject{ 41 | { 42 | Kind: "User", 43 | Name: userName, 44 | }, 45 | }, 46 | RoleRef: rbacv1.RoleRef{ 47 | Kind: "ClusterRole", 48 | Name: "cluster-admin", 49 | }, 50 | }, metav1.CreateOptions{}) 51 | if err != nil { 52 | t.Fatalf("error creating clusterrolebinding: %v\n", err) 53 | } 54 | 55 | t.Log("Testing authentication") 56 | _, err = execClient.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{}) 57 | if err != nil { 58 | t.Fatalf("error listing pods: %v\n", err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/integration/testutils/certs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 by the contributors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package testutils 18 | 19 | import ( 20 | "crypto/x509" 21 | "encoding/pem" 22 | "fmt" 23 | "os" 24 | 25 | "github.com/sirupsen/logrus" 26 | ) 27 | 28 | func LoadX509Certificate(certPath string) (*x509.Certificate, error) { 29 | if _, err := os.Stat(certPath); os.IsNotExist(err) { 30 | return nil, nil 31 | } 32 | 33 | certPEMBlock, err := os.ReadFile(certPath) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | block, _ := pem.Decode(certPEMBlock) 39 | if block == nil { 40 | return nil, fmt.Errorf("pem.Decode failed") 41 | } 42 | 43 | cert, err := x509.ParseCertificate(block.Bytes) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | logrus.WithFields(logrus.Fields{ 49 | "certPath": certPath, 50 | }).Info("loaded existing certificate") 51 | return cert, nil 52 | } 53 | -------------------------------------------------------------------------------- /tests/integration/testutils/kubeconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 by the contributors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package testutils 18 | 19 | import ( 20 | "crypto/x509" 21 | "text/template" 22 | 23 | "github.com/sirupsen/logrus" 24 | 25 | "sigs.k8s.io/aws-iam-authenticator/pkg/config/certs" 26 | "sigs.k8s.io/aws-iam-authenticator/pkg/config/kubeconfig" 27 | ) 28 | 29 | // CreateAPIServerClientKubeconfig will create a kubeconfig for the api server client 30 | func CreateAPIServerClientKubeconfig(cert *x509.Certificate, token string, kubeconfigPath, serverURL string) error { 31 | logrus.WithField("kubeconfigPath", kubeconfigPath).Info("writing api server client kubeconfig file") 32 | 33 | return kubeconfig.KubeconfigParams{ 34 | ServerURL: serverURL, 35 | CertificateAuthorityBase64: certs.CertToPEMBase64(cert.Raw), 36 | Token: token, 37 | }.WriteKubeconfig(kubeconfigPath, ApiServerClientKubeconfigTemplate) 38 | } 39 | 40 | var ApiServerClientKubeconfigTemplate = template.Must( 41 | template.New("apiserver.kubeconfig").Option("missingkey=error").Parse(` 42 | clusters: 43 | - name: kubernetes 44 | cluster: 45 | certificate-authority-data: {{.CertificateAuthorityBase64}} 46 | server: {{.ServerURL}} 47 | current-context: kubernetes 48 | contexts: 49 | - name: kubernetes 50 | context: 51 | cluster: kubernetes 52 | user: kubernetes-client 53 | users: 54 | - name: kubernetes-client 55 | user: 56 | token: {{.Token}} 57 | `)) 58 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 0.7.2 2 | --------------------------------------------------------------------------------