├── .github ├── CODEOWNERS └── workflows │ └── unit-tests.yml ├── .gitignore ├── .goreleaser.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── NOTICES.txt ├── README.md ├── SECURITY.md ├── bin ├── Dockerfile.releaser ├── build ├── build_release ├── build_utils ├── publish └── test_unit ├── cmd └── sidecar-injector │ └── main.go ├── deployment ├── crd.yaml ├── deployment.yaml.sh ├── mutatingwebhook.yaml ├── service.yaml ├── webhook-create-signed-cert.sh └── webhook-patch-ca-bundle.sh ├── design ├── admission-controller-phases.png └── sidecar-injector-ga-design.md ├── go.mod ├── go.sum ├── helm └── cyberark-sidecar-injector │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── crd.yaml │ ├── deployment.yaml │ ├── entitlements.yaml │ ├── mutatingwebhook.yaml │ ├── secret.yaml │ ├── service.yaml │ ├── service_account.yaml │ └── serviceaccountsecret.yaml │ └── values.yaml ├── pkg ├── inject │ ├── authenticator.go │ ├── authenticator_test.go │ ├── common.go │ ├── config.go │ ├── constants.go │ ├── patch.go │ ├── secretless.go │ ├── secretless_test.go │ ├── secrets-provider.go │ ├── secrets-provider_test.go │ ├── server.go │ ├── testdata │ │ ├── authenticator-admission-request.tmpl.json │ │ ├── authenticator-annotated-pod-with-image.json │ │ ├── authenticator-annotated-pod.json │ │ ├── authenticator-mutated-pod-with-image.json │ │ ├── authenticator-mutated-pod.json │ │ ├── secretless-annotated-pod-with-image.json │ │ ├── secretless-annotated-pod.json │ │ ├── secretless-mutated-pod-with-image.json │ │ ├── secretless-mutated-pod.json │ │ ├── secrets-provider-annotated-pod.json │ │ ├── secrets-provider-init-annotated-pod.json │ │ ├── secrets-provider-init-mutated-pod.json │ │ └── secrets-provider-mutated-pod.json │ └── utils_test.go └── version │ ├── version.go │ └── version_test.go ├── run-local-tests ├── run-tests └── tests ├── Dockerfile ├── echoserver.yaml.sh ├── platform_login ├── secrets.yml ├── tests_within_docker └── utils /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @cyberark/community-and-integrations-team @conjurinc/community-and-integrations-team @conjurdemos/community-and-integrations-team 2 | 3 | # Changes to .trivyignore require Security Architect approval 4 | .trivyignore @cyberark/security-architects @conjurinc/security-architects @conjurdemos/security-architects 5 | 6 | # Changes to .codeclimate.yml require Quality Architect approval 7 | .codeclimate.yml @cyberark/quality-architects @conjurinc/quality-architects @conjurdemos/quality-architects 8 | # Changes to SECURITY.md require Security Architect approval 9 | SECURITY.md @cyberark/security-architects @conjurinc/security-architects @conjurdemos/security-architects 10 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | on: 3 | # Only run this on pushes to 4 | push: 5 | branches: 6 | - main 7 | 8 | # And when PRs operations are done 9 | pull_request: 10 | types: 11 | - opened 12 | - reopened 13 | - synchronize 14 | 15 | jobs: 16 | test: 17 | name: Run Tests 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Set up Go 1.19 22 | uses: actions/setup-go@v1 23 | with: 24 | go-version: 1.19 25 | id: go 26 | 27 | - name: Check out code into the Go module directory 28 | uses: actions/checkout@v2 29 | 30 | - name: Run Tests 31 | run: go test -v -count=1 ./... 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | vendor/* 14 | 15 | # Generated files 16 | deployment/mutatingwebhook-ca-bundle.yaml 17 | deployment/deployment.yaml 18 | tests/echoserver.yaml 19 | dist 20 | 21 | # IDE files 22 | .idea 23 | 24 | # Image scan files 25 | scan_results-sidecar-injector-unfixed.json 26 | scan_results-sidecar-injector-unfixed.xml 27 | scan_results-sidecar-injector.json 28 | scan_results-sidecar-injector.xml 29 | 30 | # Temporary directory to store the CyberArk proxy CA certificate 31 | build_ca_certificate/ 32 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: cyberark-sidecar-injector 2 | 3 | builds: 4 | - id: cyberark-sidecar-injector-linux 5 | main: ./cmd/sidecar-injector 6 | env: 7 | - CGO_ENABLED=0 8 | # Tag 'netgo' is a Go build tag that ensures a pure Go networking stack 9 | # in the resulting binary instead of using the default host's stack to 10 | # ensure a fully static artifact that has no dependencies. 11 | flags: 12 | - -tags=netgo 13 | goos: 14 | - linux 15 | goarch: 16 | - amd64 17 | # The `gitCommitShort` override is there to provide the git commit information in the 18 | # final binary. 19 | ldflags: -s -w -linkmode external -X github.com/cyberark/sidecar-injector/pkg/version.gitCommitShort={{ .ShortCommit }}" -extldflags "-static" 20 | hooks: 21 | post: 22 | # Copy the binary out into the path, and give the copy the name we want 23 | # in the release . 24 | # e.g. Suppose a windows amd64 build generates a binary at 25 | # path/to/secretless-broker.exe. This will be copied to 26 | # path/to/../secretless-broker-windows_amd64.exe. The copy path can then be added to 27 | # the release and will result in a release artifact with the name 28 | # secretless-broker-windows_amd64.exe. 29 | - cp "{{ .Path }}" "{{ dir .Path }}/../cyberark-sidecar-injector-{{.Target}}{{.Ext}}" 30 | 31 | archives: 32 | - id: cyberark-sidecar-injector 33 | files: 34 | - CHANGELOG.md 35 | - NOTICES.txt 36 | - LICENSE 37 | name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}_{{.Arch}}" 38 | wrap_in_directory: true 39 | 40 | checksum: 41 | name_template: 'SHA256SUMS.txt' 42 | 43 | dist: ./dist/goreleaser 44 | 45 | snapshot: 46 | name_template: "{{ .Tag }}-next" 47 | 48 | release: 49 | disable: false 50 | draft: true 51 | extra_files: 52 | - glob: NOTICES.txt 53 | - glob: LICENSE 54 | - glob: CHANGELOG.md 55 | - glob: dist/goreleaser/cyberark-sidecar-injector-linux_amd64 56 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.2.0] - 2023-03-14 10 | 11 | ### Added 12 | - Add support for selection of sidecar container versions 13 | [cyberark/sidecar-injector#71](https://github.com/cyberark/sidecar-injector/pull/71) 14 | - Add ability to set (and override default) deployment resource `apiVersion` in manifests. 15 | [cyberark/sidecar-injector#27](https://github.com/cyberark/sidecar-injector/pull/27) 16 | - Add support for secrets-provider-for-k8s
17 | [cyberark/sidecar-injector#72](https://github.com/cyberark/sidecar-injector/pull/72)
18 | [cyberark/sidecar-injector#73](https://github.com/cyberark/sidecar-injector/pull/73)
19 | [cyberark/sidecar-injector#74](https://github.com/cyberark/sidecar-injector/pull/74)
20 | [cyberark/sidecar-injector#79](https://github.com/cyberark/sidecar-injector/pull/79)
21 | 22 | ### Changed 23 | - Upgrade testify to 1.8.0 and k8s to 0.25.2 24 | [cyberark/sidecar-injector#77](https://github.com/cyberark/sidecar-injector/pull/78) 25 | - Upgrade Go to 1.19 26 | [cyberark/sidecar-injector#78](https://github.com/cyberark/sidecar-injector/pull/78) 27 | - Dropped support for Helm V2 and converted to Helm V3. 28 | [cyberark/sidecar-injector#60](https://github.com/cyberark/sidecar-injector/pull/60) 29 | - K8s APIs used for mutating webhook request/response messages are upgraded 30 | from the deprecated 'v1beta1' versions to 'v1' so that the Sidecar Injector 31 | works on Kubernetes v1.22 or newer and OpenShift v4.9 or newer. 32 | [cyberark/sidecar-injector#62](https://github.com/cyberark/sidecar-injector/pull/62) 33 | - BREAKING CHANGE: Changed annotations to be consistent with other Cyberark repositories. 34 | sidecar-injector.cyberark.com is changed to conjur.org 35 | All user manifests must be changed to use the new annotations. [cyberark/sidecar-injector#70](https://github.com/cyberark/sidecar-injector/pull/70) 36 | - Deployment resource `apiVersion` (in manifests) changed from `extensions/v1beta1` to 37 | `apps/v1`. [#47](https://github.com/cyberark/sidecar-injector/pull/47) 38 | - BREAKING CHANGE: Changed annotation `conjur-token-receivers` to `conjur-inject-volumes` for compatability 39 | with Secrets Provider [cyberark/sidecar-injector#76](https://github.com/cyberark/sidecar-injector/pull/76) 40 | 41 | ### Security 42 | - Update alpine base image to 3.18 and golang to 1.21 43 | [cyberark/sidecar-injector#92](https://github.com/cyberark/sidecar-injector/pull/92) 44 | - Update golang.org/x/net to v0.17.0 45 | [cyberark/sidecar-injector#92](https://github.com/cyberark/sidecar-injector/pull/92) 46 | - Upgrade google.golang.org/protobuf to v1.29.1 47 | [cyberark/sidecar-injector#89](https://github.com/cyberark/sidecar-injector/pull/89) 48 | - Upgraded golang.org/x/net and golang.org/x/text to 0.7.0 to resolve CVE-2022-41723 49 | [cyberark/sidecar-injector#86](https://github.com/cyberark/sidecar-injector/pull/86) 50 | - Forced golang.org/x/text to use 0.3.8 to resolve CVE-2022-32149 51 | [cyberark/sidecar-injector#81](https://github.com/cyberark/sidecar-injector/pull/81) 52 | - Added replace statement for golang.org/x/crypto@v0.0.0-20210921155107-089bfa567519 53 | [cyberark/sidecar-injector#80](https://github.com/cyberark/sidecar-injector/pull/80) 54 | - Updated replace statements to force golang.org/x/net v0.0.0-20220923203811-8be639271d50 55 | and updated testify to 1.8.0 [cyberark/sidecar-injector#78](https://github.com/cyberark/sidecar-injector/pull/78) 56 | - Added replace statements to go.mod to remove vulnerable dependency versions from the dependency tree 57 | [cyberark/sidecar-injector#68](https://github.com/cyberark/sidecar-injector/pull/68) 58 | [cyberark/sidecar-injector#69](https://github.com/cyberark/sidecar-injector/pull/69) 59 | - Updated golang.org/x/net to resolve CVE-2022-41721 60 | [cyberark/sidecar-injector#84](https://github.com/cyberark/sidecar-injector/pull/84) 61 | 62 | ## [0.1.0] - 2020-05-28 63 | 64 | ### Added 65 | - Functional Sidecar Injector with support for the Conjur Kubernetes Authenticator and 66 | Secretless Broker. 67 | - Helm (v2) Chart for deploying Sidecar Injector. 68 | - Ability to inject `conjur-access-token` volume mounts to a selection of containers, in 69 | tandem with the authenticator sidecar [#25](https://github.com/cyberark/sidecar-injector/issues/25). 70 | 71 | The selection of containers are specified via the 72 | `sidecar-injector.cyberark.com/conjurTokenReceivers` annotation whose value is a 73 | comma-separated list of container names. 74 | - Ability to configure the sidecar container images by specifying flags on the sidecar 75 | injector binary [#29](https://github.com/cyberark/sidecar-injector/issues/29). 76 | 77 | [Unreleased]: https://github.com/cyberark/sidecar-injector/compare/v0.1.0...HEAD 78 | [0.1.0]: https://github.com/cyberark/sidecar-injector/releases/tag/v0.1.0 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | For general contribution and community guidelines, please see the [community repo](https://github.com/cyberark/community). 4 | 5 | ## Contributing 6 | 7 | 1. [Fork the project](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) 8 | 2. [Clone your fork](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) 9 | 3. Make local changes to your fork by editing files 10 | 3. [Commit your changes](https://help.github.com/en/github/managing-files-in-a-repository/adding-a-file-to-a-repository-using-the-command-line) 11 | 4. [Push your local changes to the remote server](https://help.github.com/en/github/using-git/pushing-commits-to-a-remote-repository) 12 | 5. [Create new Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) 13 | 14 | From here your pull request will be reviewed and once you've responded to all 15 | feedback it will be merged into the project. Congratulations, you're a 16 | contributor! 17 | 18 | ## Generate the Helm package 19 | 20 | 1. Build the package tgz ( run `helm package cyberark-sidecar-injector` from the helm directory). 21 | 2. Copy that tgz to a local helm-charts repo 22 | 3. Follow instructions [here](https://github.com/cyberark/helm-charts/blob/main/CONTRIBUTING.md) 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #=============== Sidecar Injector Build Container =================== 2 | FROM golang:1.21 as mutating-webhook-service-builder 3 | 4 | ARG GIT_COMMIT_SHORT="dev" 5 | ARG KUBECTL_VERSION=1.22.0 6 | 7 | # On CyberArk dev laptops, golang module dependencies are downloaded with a 8 | # corporate proxy in the middle. For these connections to succeed we need to 9 | # configure the proxy CA certificate in build containers. 10 | # 11 | # To allow this script to also work on non-CyberArk laptops where the CA 12 | # certificate is not available, we copy the (potentially empty) directory 13 | # and update container certificates based on that, rather than rely on the 14 | # CA file itself. 15 | ADD build_ca_certificate /usr/local/share/ca-certificates/ 16 | RUN update-ca-certificates 17 | 18 | RUN mkdir -p /work 19 | WORKDIR /work 20 | 21 | ENV GOOS=linux \ 22 | GOARCH=amd64 \ 23 | CGO_ENABLED=0 24 | 25 | # Download kubectl CLI 26 | RUN curl -LO https://dl.k8s.io/release/v"${KUBECTL_VERSION}"/bin/linux/amd64/kubectl && \ 27 | chmod +x kubectl 28 | 29 | COPY go.mod go.sum ./ 30 | RUN go mod download 31 | 32 | # sidecar-injector source files 33 | COPY pkg ./pkg 34 | COPY cmd ./cmd 35 | 36 | # The `gitCommitShort` override is there to provide the git commit information in the final 37 | # binary. 38 | RUN go build \ 39 | -ldflags="-X github.com/cyberark/sidecar-injector/pkg/version.gitCommitShort=$GIT_COMMIT_SHORT" \ 40 | -o cyberark-sidecar-injector \ 41 | ./cmd/sidecar-injector 42 | 43 | #=============== Sidecar Injector Container ========================= 44 | FROM alpine:3.18 45 | 46 | RUN apk add -u shadow libc6-compat curl openssl && \ 47 | rm -rf /var/cache/apk/* 48 | 49 | # Add Limited user 50 | RUN groupadd -r sidecar-injector \ 51 | -g 777 && \ 52 | useradd -c "sidecar-injector runner account" \ 53 | -g sidecar-injector \ 54 | -u 777 \ 55 | -m \ 56 | -r \ 57 | sidecar-injector 58 | 59 | USER sidecar-injector 60 | 61 | COPY --from=mutating-webhook-service-builder \ 62 | /work/cyberark-sidecar-injector \ 63 | /work/kubectl \ 64 | /usr/local/bin/ 65 | 66 | ENTRYPOINT ["/usr/local/bin/cyberark-sidecar-injector"] 67 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | pipeline { 4 | agent { label 'executor-v2' } 5 | 6 | options { 7 | timestamps() 8 | buildDiscarder(logRotator(numToKeepStr: '30')) 9 | } 10 | 11 | triggers { 12 | cron(getDailyCronString()) 13 | } 14 | 15 | stages { 16 | stage('Validate') { 17 | parallel { 18 | stage('Changelog') { 19 | steps { sh 'docker run --rm --volume "${PWD}/CHANGELOG.md":/CHANGELOG.md cyberark/parse-a-changelog' } 20 | } 21 | } 22 | } 23 | 24 | stage('Image Build') { 25 | steps { 26 | sh './bin/build' 27 | } 28 | } 29 | stage('Test Sidecar Injector'){ 30 | steps { 31 | sh 'summon -f ./tests/secrets.yml ./run-tests' 32 | } 33 | } 34 | 35 | stage('Scan Image') { 36 | parallel { 37 | stage("Scan image for fixable issues") { 38 | steps { 39 | scanAndReport("sidecar-injector:latest", "HIGH", false) 40 | } 41 | } 42 | stage("Scan image for all issues") { 43 | steps { 44 | scanAndReport("sidecar-injector:latest", "NONE", true) 45 | } 46 | } 47 | } 48 | } 49 | 50 | 51 | stage('Publish Edge Sidecar Injector Images') { 52 | when { 53 | branch 'main' 54 | } 55 | 56 | steps { 57 | sh './bin/publish edge' 58 | } 59 | } 60 | 61 | stage('Release') { 62 | // Only run this stage when triggered by a tag 63 | when { tag "v*" } 64 | 65 | parallel { 66 | stage('Publish Tagged Sidecar Injector Images') { 67 | steps { 68 | // The tag trigger sets TAG_NAME as an environment variable 69 | sh './bin/publish' 70 | } 71 | } 72 | stage('Create draft release') { 73 | steps { 74 | sh "summon --yaml 'GITHUB_TOKEN: !var github/users/conjur-jenkins/api-token' ./bin/build_release" 75 | archiveArtifacts 'dist/goreleaser/' 76 | } 77 | } 78 | } 79 | } 80 | 81 | } 82 | 83 | post { 84 | always { 85 | cleanupAndNotify(currentBuild.currentResult) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright (c) 2020 CyberArk Software Ltd. All rights reserved. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICES.txt: -------------------------------------------------------------------------------- 1 | =============== TABLE OF CONTENTS ============================= 2 | 3 | The following is a listing of the cyberark/sidecar-injector open source components detailed 4 | in this document. This list is provided for your convenience; please read 5 | further if you wish to review the copyright notice(s) and the full text 6 | of the license associated with each component. 7 | 8 | 9 | Section 1: Apache-2.0 10 | >>> k8s.io/api v0.25.2 11 | >>> k8s.io/apimachinery v0.25.2 12 | 13 | Section 2: MIT 14 | >>> github.com/stretchr/testify v1.8.0 15 | 16 | Section 3: BSD-3-Clause 17 | >>> github.com/evanphx/json-patch v5.6.0+incompatible 18 | 19 | 20 | APPENDIX: Standard License Files and Templates 21 | 22 | >>> Apache-2.0 23 | >>> MIT 24 | >>> BSD-3-Clause 25 | 26 | --------------- SECTION 1: Apache-2.0 ---------- 27 | 28 | Apache-2.0 License is applicable to the following component(s). 29 | 30 | >>> k8s.io/api v0.25.2 31 | Copyright 32 | 33 | Licensed under the Apache License, Version 2.0 (the "License"); 34 | you may not use this file except in compliance with the License. 35 | You may obtain a copy of the License at 36 | 37 | http://www.apache.org/licenses/LICENSE-2.0 38 | 39 | Unless required by applicable law or agreed to in writing, software 40 | distributed under the License is distributed on an "AS IS" BASIS, 41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | See the License for the specific language governing permissions and 43 | limitations under the License. 44 | 45 | >>> k8s.io/apimachinery v0.25.2 46 | Copyright 47 | 48 | Licensed under the Apache License, Version 2.0 (the "License"); 49 | you may not use this file except in compliance with the License. 50 | You may obtain a copy of the License at 51 | 52 | http://www.apache.org/licenses/LICENSE-2.0 53 | 54 | Unless required by applicable law or agreed to in writing, software 55 | distributed under the License is distributed on an "AS IS" BASIS, 56 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 57 | See the License for the specific language governing permissions and 58 | limitations under the License. 59 | 60 | 61 | --------------- SECTION 2: MIT ---------- 62 | 63 | MIT License is applicable to the following component(s). 64 | 65 | >>> github.com/stretchr/testify v1.8.0 66 | 67 | Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell 68 | 69 | Permission is hereby granted, free of charge, to any person obtaining a copy 70 | of this software and associated documentation files (the "Software"), to deal 71 | in the Software without restriction, including without limitation the rights 72 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 73 | copies of the Software, and to permit persons to whom the Software is 74 | furnished to do so, subject to the following conditions: 75 | 76 | The above copyright notice and this permission notice shall be included in all 77 | copies or substantial portions of the Software. 78 | 79 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 80 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 81 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 82 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 83 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 84 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 85 | SOFTWARE. 86 | 87 | 88 | --------------- SECTION 3: BSD-3-Clause ---------- 89 | 90 | BSD-3-Clause License is applicable to the following component(s). 91 | 92 | >>> github.com/evanphx/json-patch v5.6.0+incompatible 93 | 94 | Copyright (c) 2014, Evan Phoenix 95 | All rights reserved. 96 | 97 | Redistribution and use in source and binary forms, with or without 98 | modification, are permitted provided that the following conditions are met: 99 | 100 | * Redistributions of source code must retain the above copyright notice, this 101 | list of conditions and the following disclaimer. 102 | * Redistributions in binary form must reproduce the above copyright notice 103 | this list of conditions and the following disclaimer in the documentation 104 | and/or other materials provided with the distribution. 105 | * Neither the name of the Evan Phoenix nor the names of its contributors 106 | may be used to endorse or promote products derived from this software 107 | without specific prior written permission. 108 | 109 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 110 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 111 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 112 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 113 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 114 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 115 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 116 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 117 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 118 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 119 | 120 | 121 | =============== APPENDIX: License Files and Templates ============== 122 | 123 | 124 | 125 | --------------- APPENDIX 1: Apache-2.0 License (Template) ----------- 126 | 127 | Apache License 128 | Version 2.0, January 2004 129 | http://www.apache.org/licenses/ 130 | 131 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 132 | 133 | 1. Definitions. 134 | 135 | "License" shall mean the terms and conditions for use, reproduction, 136 | and distribution as defined by Sections 1 through 9 of this document. 137 | 138 | "Licensor" shall mean the copyright owner or entity authorized by 139 | the copyright owner that is granting the License. 140 | 141 | "Legal Entity" shall mean the union of the acting entity and all 142 | other entities that control, are controlled by, or are under common 143 | control with that entity. For the purposes of this definition, 144 | "control" means (i) the power, direct or indirect, to cause the 145 | direction or management of such entity, whether by contract or 146 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 147 | outstanding shares, or (iii) beneficial ownership of such entity. 148 | 149 | "You" (or "Your") shall mean an individual or Legal Entity 150 | exercising permissions granted by this License. 151 | 152 | "Source" form shall mean the preferred form for making modifications, 153 | including but not limited to software source code, documentation 154 | source, and configuration files. 155 | 156 | "Object" form shall mean any form resulting from mechanical 157 | transformation or translation of a Source form, including but 158 | not limited to compiled object code, generated documentation, 159 | and conversions to other media types. 160 | 161 | "Work" shall mean the work of authorship, whether in Source or 162 | Object form, made available under the License, as indicated by a 163 | copyright notice that is included in or attached to the work 164 | (an example is provided in the Appendix below). 165 | 166 | "Derivative Works" shall mean any work, whether in Source or Object 167 | form, that is based on (or derived from) the Work and for which the 168 | editorial revisions, annotations, elaborations, or other modifications 169 | represent, as a whole, an original work of authorship. For the purposes 170 | of this License, Derivative Works shall not include works that remain 171 | separable from, or merely link (or bind by name) to the interfaces of, 172 | the Work and Derivative Works thereof. 173 | 174 | "Contribution" shall mean any work of authorship, including 175 | the original version of the Work and any modifications or additions 176 | to that Work or Derivative Works thereof, that is intentionally 177 | submitted to Licensor for inclusion in the Work by the copyright owner 178 | or by an individual or Legal Entity authorized to submit on behalf of 179 | the copyright owner. For the purposes of this definition, "submitted" 180 | means any form of electronic, verbal, or written communication sent 181 | to the Licensor or its representatives, including but not limited to 182 | communication on electronic mailing lists, source code control systems, 183 | and issue tracking systems that are managed by, or on behalf of, the 184 | Licensor for the purpose of discussing and improving the Work, but 185 | excluding communication that is conspicuously marked or otherwise 186 | designated in writing by the copyright owner as "Not a Contribution." 187 | 188 | "Contributor" shall mean Licensor and any individual or Legal Entity 189 | on behalf of whom a Contribution has been received by Licensor and 190 | subsequently incorporated within the Work. 191 | 192 | 2. Grant of Copyright License. Subject to the terms and conditions of 193 | this License, each Contributor hereby grants to You a perpetual, 194 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 195 | copyright license to reproduce, prepare Derivative Works of, 196 | publicly display, publicly perform, sublicense, and distribute the 197 | Work and such Derivative Works in Source or Object form. 198 | 199 | 3. Grant of Patent License. Subject to the terms and conditions of 200 | this License, each Contributor hereby grants to You a perpetual, 201 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 202 | (except as stated in this section) patent license to make, have made, 203 | use, offer to sell, sell, import, and otherwise transfer the Work, 204 | where such license applies only to those patent claims licensable 205 | by such Contributor that are necessarily infringed by their 206 | Contribution(s) alone or by combination of their Contribution(s) 207 | with the Work to which such Contribution(s) was submitted. If You 208 | institute patent litigation against any entity (including a 209 | cross-claim or counterclaim in a lawsuit) alleging that the Work 210 | or a Contribution incorporated within the Work constitutes direct 211 | or contributory patent infringement, then any patent licenses 212 | granted to You under this License for that Work shall terminate 213 | as of the date such litigation is filed. 214 | 215 | 4. Redistribution. You may reproduce and distribute copies of the 216 | Work or Derivative Works thereof in any medium, with or without 217 | modifications, and in Source or Object form, provided that You 218 | meet the following conditions: 219 | 220 | (a) You must give any other recipients of the Work or 221 | Derivative Works a copy of this License; and 222 | 223 | (b) You must cause any modified files to carry prominent notices 224 | stating that You changed the files; and 225 | 226 | (c) You must retain, in the Source form of any Derivative Works 227 | that You distribute, all copyright, patent, trademark, and 228 | attribution notices from the Source form of the Work, 229 | excluding those notices that do not pertain to any part of 230 | the Derivative Works; and 231 | 232 | (d) If the Work includes a "NOTICE" text file as part of its 233 | distribution, then any Derivative Works that You distribute must 234 | include a readable copy of the attribution notices contained 235 | within such NOTICE file, excluding those notices that do not 236 | pertain to any part of the Derivative Works, in at least one 237 | of the following places: within a NOTICE text file distributed 238 | as part of the Derivative Works; within the Source form or 239 | documentation, if provided along with the Derivative Works; or, 240 | within a display generated by the Derivative Works, if and 241 | wherever such third-party notices normally appear. The contents 242 | of the NOTICE file are for informational purposes only and 243 | do not modify the License. You may add Your own attribution 244 | notices within Derivative Works that You distribute, alongside 245 | or as an addendum to the NOTICE text from the Work, provided 246 | that such additional attribution notices cannot be construed 247 | as modifying the License. 248 | 249 | You may add Your own copyright statement to Your modifications and 250 | may provide additional or different license terms and conditions 251 | for use, reproduction, or distribution of Your modifications, or 252 | for any such Derivative Works as a whole, provided Your use, 253 | reproduction, and distribution of the Work otherwise complies with 254 | the conditions stated in this License. 255 | 256 | 5. Submission of Contributions. Unless You explicitly state otherwise, 257 | any Contribution intentionally submitted for inclusion in the Work 258 | by You to the Licensor shall be under the terms and conditions of 259 | this License, without any additional terms or conditions. 260 | Notwithstanding the above, nothing herein shall supersede or modify 261 | the terms of any separate license agreement you may have executed 262 | with Licensor regarding such Contributions. 263 | 264 | 6. Trademarks. This License does not grant permission to use the trade 265 | names, trademarks, service marks, or product names of the Licensor, 266 | except as required for reasonable and customary use in describing the 267 | origin of the Work and reproducing the content of the NOTICE file. 268 | 269 | 7. Disclaimer of Warranty. Unless required by applicable law or 270 | agreed to in writing, Licensor provides the Work (and each 271 | Contributor provides its Contributions) on an "AS IS" BASIS, 272 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 273 | implied, including, without limitation, any warranties or conditions 274 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 275 | PARTICULAR PURPOSE. You are solely responsible for determining the 276 | appropriateness of using or redistributing the Work and assume any 277 | risks associated with Your exercise of permissions under this License. 278 | 279 | 8. Limitation of Liability. In no event and under no legal theory, 280 | whether in tort (including negligence), contract, or otherwise, 281 | unless required by applicable law (such as deliberate and grossly 282 | negligent acts) or agreed to in writing, shall any Contributor be 283 | liable to You for damages, including any direct, indirect, special, 284 | incidental, or consequential damages of any character arising as a 285 | result of this License or out of the use or inability to use the 286 | Work (including but not limited to damages for loss of goodwill, 287 | work stoppage, computer failure or malfunction, or any and all 288 | other commercial damages or losses), even if such Contributor 289 | has been advised of the possibility of such damages. 290 | 291 | 9. Accepting Warranty or Additional Liability. While redistributing 292 | the Work or Derivative Works thereof, You may choose to offer, 293 | and charge a fee for, acceptance of support, warranty, indemnity, 294 | or other liability obligations and/or rights consistent with this 295 | License. However, in accepting such obligations, You may act only 296 | on Your own behalf and on Your sole responsibility, not on behalf 297 | of any other Contributor, and only if You agree to indemnify, 298 | defend, and hold each Contributor harmless for any liability 299 | incurred by, or claims asserted against, such Contributor by reason 300 | of your accepting any such warranty or additional liability. 301 | 302 | END OF TERMS AND CONDITIONS 303 | 304 | APPENDIX: How to apply the Apache License to your work. 305 | 306 | To apply the Apache License to your work, attach the following 307 | boilerplate notice, with the fields enclosed by brackets "[]" 308 | replaced with your own identifying information. (Don't include 309 | the brackets!) The text should be enclosed in the appropriate 310 | comment syntax for the file format. We also recommend that a 311 | file or class name and description of purpose be included on the 312 | same "printed page" as the copyright notice for easier 313 | identification within third-party archives. 314 | 315 | Copyright 316 | 317 | Licensed under the Apache License, Version 2.0 (the "License"); 318 | you may not use this file except in compliance with the License. 319 | You may obtain a copy of the License at 320 | 321 | http://www.apache.org/licenses/LICENSE-2.0 322 | 323 | Unless required by applicable law or agreed to in writing, software 324 | distributed under the License is distributed on an "AS IS" BASIS, 325 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 326 | See the License for the specific language governing permissions and 327 | limitations under the License. 328 | 329 | --------------- APPENDIX 2: MIT License (Template) ----------- 330 | 331 | Copyright 332 | 333 | Permission is hereby granted, free of charge, to any person obtaining a copy 334 | of this software and associated documentation files (the "Software"), to deal 335 | in the Software without restriction, including without limitation the rights 336 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 337 | copies of the Software, and to permit persons to whom the Software is 338 | furnished to do so, subject to the following conditions: 339 | 340 | The above copyright notice and this permission notice shall be included in all 341 | copies or substantial portions of the Software. 342 | 343 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 344 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 345 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 346 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 347 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 348 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 349 | SOFTWARE. 350 | 351 | 352 | --------------- APPENDIX 3: BSD-3-Clause License (Template) ----------- 353 | 354 | Copyright 355 | All rights reserved. 356 | 357 | Redistribution and use in source and binary forms, with or without 358 | modification, are permitted provided that the following conditions are met: 359 | 360 | * Redistributions of source code must retain the above copyright notice, this 361 | list of conditions and the following disclaimer. 362 | * Redistributions in binary form must reproduce the above copyright notice 363 | this list of conditions and the following disclaimer in the documentation 364 | and/or other materials provided with the distribution. 365 | * Neither the name of the Evan Phoenix nor the names of its contributors 366 | may be used to endorse or promote products derived from this software 367 | without specific prior written permission. 368 | 369 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 370 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 371 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 372 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 373 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 374 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 375 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 376 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 377 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 378 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 379 | 380 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policies and Procedures 2 | 3 | This document outlines security procedures and general policies for the CyberArk Conjur 4 | suite of tools and products. 5 | 6 | * [Reporting a Bug](#reporting-a-bug) 7 | * [Disclosure Policy](#disclosure-policy) 8 | * [Comments on this Policy](#comments-on-this-policy) 9 | 10 | ## Reporting a Bug 11 | 12 | The CyberArk Conjur team and community take all security bugs in the Conjur suite seriously. 13 | Thank you for improving the security of the Conjur suite. We appreciate your efforts and 14 | responsible disclosure and will make every effort to acknowledge your 15 | contributions. 16 | 17 | Report security bugs by emailing the lead maintainers at security@conjur.org. 18 | 19 | The maintainers will acknowledge your email within 2 business days. Subsequently, we will 20 | send a more detailed response within 2 business days of our acknowledgement indicating 21 | the next steps in handling your report. After the initial reply to your report, the security 22 | team will endeavor to keep you informed of the progress towards a fix and full 23 | announcement, and may ask for additional information or guidance. 24 | 25 | Report security bugs in third-party modules to the person or team maintaining 26 | the module. 27 | 28 | ## Disclosure Policy 29 | 30 | When the security team receives a security bug report, they will assign it to a 31 | primary handler. This person will coordinate the fix and release process, 32 | involving the following steps: 33 | 34 | * Confirm the problem and determine the affected versions. 35 | * Audit code to find any potential similar problems. 36 | * Prepare fixes for all releases still under maintenance. These fixes will be 37 | released as fast as possible. 38 | 39 | ## Comments on this Policy 40 | 41 | If you have suggestions on how this process could be improved please submit a 42 | pull request. 43 | -------------------------------------------------------------------------------- /bin/Dockerfile.releaser: -------------------------------------------------------------------------------- 1 | FROM dockercore/golang-cross 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y bash \ 5 | build-essential \ 6 | curl \ 7 | docker \ 8 | git \ 9 | mercurial \ 10 | rpm 11 | 12 | RUN curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh 13 | 14 | ENTRYPOINT ["goreleaser"] 15 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # 3 | # Build CyberArk sidecar injector mutating webhook service 4 | # usage: ./bin/build 5 | set -ex 6 | 7 | cd "$(dirname "${0}")" 8 | . ./build_utils 9 | cd .. 10 | 11 | readonly IMAGE_NAME="sidecar-injector" 12 | 13 | function main() { 14 | retrieve_cyberark_ca_cert 15 | build_docker_image 16 | } 17 | 18 | function build_docker_image() { 19 | docker build \ 20 | --build-arg "GIT_COMMIT_SHORT=$(git_commit_short)" \ 21 | -t "${IMAGE_NAME}:latest" \ 22 | -t "${IMAGE_NAME}:$(full_version_tag)" \ 23 | . 24 | } 25 | 26 | main 27 | -------------------------------------------------------------------------------- /bin/build_release: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | CURRENT_DIR=$(cd $(dirname "$0"); pwd) 4 | 5 | # xgo because it allows cross-compilation 6 | GORELEASER_IMAGE="cyberark/goreleaser:latest-xgo" 7 | 8 | echo "Current dir: $CURRENT_DIR" 9 | 10 | # TODO: The image cyberark/goreleaser:latest-xgo will need to be pushed to Dockerhub, and 11 | # the command below should become `docker pull cyberark/goreleaser:latest-xgo` 12 | 13 | # NOTE: Piping the Dockerfile sends an empty context to docker build 14 | docker build -t "${GORELEASER_IMAGE}" - < "$CURRENT_DIR/Dockerfile.releaser" 15 | 16 | docker run --rm -t \ 17 | --env GITHUB_TOKEN \ 18 | --volume "$CURRENT_DIR/..:/work" \ 19 | --workdir /work \ 20 | "${GORELEASER_IMAGE}" --rm-dist "$@" 21 | 22 | echo "Releases built. Archives can be found in dist/goreleaser" 23 | -------------------------------------------------------------------------------- /bin/build_utils: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | #### 6 | # Functions to generate version numbers for this project 7 | #### 8 | 9 | readonly VERSION_GO_FILE="pkg/version/version.go" 10 | 11 | function git_version() { 12 | grep -v '^//' "${VERSION_GO_FILE}" | grep 'const gitVersion =' | awk -F'= ' '{print $2}' | tr -d '"' 13 | } 14 | 15 | function git_commit_short() { 16 | git rev-parse --short HEAD 17 | } 18 | 19 | function full_version_tag() { 20 | echo "$(git_version)-$(git_commit_short)" 21 | } 22 | 23 | # generate less specific versions, eg. given 1.2.3 will print 1.2 and 1 24 | # (note: the argument itself is not printed, append it explicitly if needed) 25 | function gen_versions() { 26 | local version=$1 27 | while [[ $version = *.* ]]; do 28 | version=${version%.*} 29 | echo $version 30 | done 31 | } 32 | 33 | function retrieve_cyberark_ca_cert() { 34 | # On CyberArk dev laptops, golang module dependencies are downloaded with a 35 | # corporate proxy in the middle. For these connections to succeed we need to 36 | # configure the proxy CA certificate in build containers. 37 | # 38 | # To allow this script to also work on non-CyberArk laptops where the CA 39 | # certificate is not available, we update container certificates based on 40 | # a (potentially empty) certificate directory, rather than relying on the 41 | # CA file itself. 42 | mkdir -p "$(repo_root)/build_ca_certificate" 43 | 44 | # Only attempt to extract the certificate if the security 45 | # command is available. 46 | # 47 | # The certificate file must have the .crt extension to be imported 48 | # by `update-ca-certificates`. 49 | if command -v security &> /dev/null 50 | then 51 | security find-certificate \ 52 | -a -c "CyberArk Enterprise Root CA" \ 53 | -p > build_ca_certificate/cyberark_root.crt 54 | fi 55 | } 56 | 57 | repo_root() { 58 | git rev-parse --show-toplevel 59 | } 60 | -------------------------------------------------------------------------------- /bin/publish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | cd "$(dirname "${0}")" 6 | . ./build_utils 7 | cd .. 8 | 9 | readonly VERSION="$(git_version)" 10 | readonly REGISTRY="cyberark" 11 | readonly IMAGE_NAME="sidecar-injector" 12 | readonly LOCAL_IMAGE_WITH_TAG="${IMAGE_NAME}:$(full_version_tag)" 13 | 14 | function publish_image() { 15 | local tag=${1} 16 | image_with_version_tag="${REGISTRY}/${IMAGE_NAME}:${tag}" 17 | echo "Tagging and pushing ${image_with_version_tag}" 18 | 19 | docker tag "${LOCAL_IMAGE_WITH_TAG}" "${image_with_version_tag}" 20 | docker push "${image_with_version_tag}" 21 | } 22 | 23 | function main() { 24 | # If an argument is passed to the script it will be read as a tag, and this single tag 25 | # will be published then the script will exit. This functionality is useful for 26 | # publishing single tags without involving the tag-triggered builds logic below. 27 | if [[ $# -gt 0 ]] 28 | then 29 | publish_image $1 30 | exit 0; 31 | fi 32 | 33 | if [[ -z "${TAG_NAME:-}" ]]; then 34 | echo "Please supply environment variable TAG_NAME." 35 | echo "If you see this error in Jenkins it means the publish script was run" 36 | echo "for a build that wasn't triggered by a tag - please check publish stage conditions." 37 | exit 1 38 | fi 39 | 40 | local tag_version="${TAG_NAME#v}" 41 | # Ensure TAG_NAME version matches project version 42 | if [[ "${tag_version}" != "${VERSION}" ]]; then 43 | echo "TAG_NAME envvar version was '${tag_version}', expected '${VERSION}'"; 44 | echo "TAG_NAME envvar version must match project version"; 45 | exit 1; 46 | fi 47 | 48 | # Accumulate TAGS 49 | # 50 | 51 | local TAGS=( 52 | "${VERSION}" 53 | ) 54 | 55 | # Add less specific version tags, eg. given 1.2.3, add 1.2 and 1 56 | for tag in $(gen_versions "${VERSION}"); do 57 | TAGS+=("${tag}") 58 | done 59 | 60 | # Add 'latest' tag only when TAG_NAME is the highest semantic tag 61 | local LATEST_TAG 62 | LATEST_TAG="$(git tag | sort -r --version-sort)" 63 | if [[ "${TAG_NAME}" == "${LATEST_TAG}" ]]; then 64 | TAGS+=('latest') 65 | fi 66 | 67 | # Publish 68 | # 69 | 70 | # Publish tags 71 | for tag in "${TAGS[@]}"; do 72 | publish_image "${tag}" 73 | done 74 | } 75 | 76 | main "$@" 77 | -------------------------------------------------------------------------------- /bin/test_unit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eox pipefail 4 | 5 | cd $(dirname $0) 6 | 7 | function main() { 8 | build_image 9 | run_unit_tests $1 10 | } 11 | 12 | 13 | function run_unit_tests() { 14 | echo "Running unit tests..." 15 | cd ../pkg/inject 16 | if [ "$1" ]; then 17 | go test -v -run $1 18 | else 19 | go test -v 20 | fi 21 | echo "Unit test exit status: $?" 22 | } 23 | 24 | function build_image() { 25 | echo "Building image..." 26 | ./build 27 | } 28 | 29 | main $1 30 | -------------------------------------------------------------------------------- /cmd/sidecar-injector/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | 13 | "github.com/cyberark/sidecar-injector/pkg/inject" 14 | "github.com/cyberark/sidecar-injector/pkg/version" 15 | ) 16 | // Define environment variables used in Secrets Provider config 17 | 18 | func main() { 19 | var parameters inject.WebhookServerParameters 20 | 21 | // Reset flag package to avoid pollution by glog, which is an indirect dependency 22 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) 23 | 24 | // retrieve command line parameters 25 | flag.IntVar(¶meters.Port, "port", 443, "Webhook server port.") 26 | flag.StringVar(¶meters.CertFile, "tlsCertFile", "/etc/webhook/certs/cert.pem", "Path to file containing the x509 Certificate for HTTPS.") 27 | flag.StringVar(¶meters.KeyFile, "tlsKeyFile", "/etc/webhook/certs/key.pem", "Path to file containing the x509 Private Key for HTTPS.") 28 | flag.BoolVar(¶meters.NoHTTPS, "noHTTPS", false, "Run Webhook server as HTTP (not HTTPS).") 29 | flag.StringVar(¶meters.SecretlessContainerImage, "secretless-image", "cyberark/secretless-broker:latest", "Container image for the Secretless sidecar") 30 | flag.StringVar(¶meters.AuthenticatorContainerImage, "authenticator-image", "cyberark/conjur-kubernetes-authenticator:latest", "Container image for the Kubernetes Authenticator sidecar") 31 | flag.StringVar(¶meters.SecretsProviderContainerImage, "secrets-provider-image", "cyberark/secrets-provider-for-k8s:latest", "Container image for the Secrets Provider sidecar") 32 | 33 | // Flag.parse only covers `-version` flag but for `version`, we need to explicitly 34 | // check the args 35 | showVersion := flag.Bool("version", false, "Show current version") 36 | 37 | flag.Parse() 38 | 39 | // Either the flag or the arg should be enough to show the version 40 | if *showVersion || flag.Arg(0) == "version" { 41 | fmt.Printf("cyberark-sidecar-injector v%s\n", version.Get()) 42 | return 43 | } 44 | 45 | log.Printf("cyberark-sidecar-injector v%s starting up...", version.Get()) 46 | 47 | whsvr := &inject.WebhookServer{ 48 | Params: parameters, 49 | Server: &http.Server{ 50 | Addr: fmt.Sprintf(":%v", parameters.Port), 51 | TLSConfig: nil, 52 | }, 53 | } 54 | 55 | // define http server and server handler 56 | mux := http.NewServeMux() 57 | mux.HandleFunc("/mutate", whsvr.Serve) 58 | whsvr.Server.Handler = mux 59 | 60 | // start webhook server in goroutine 61 | go func() { 62 | log.Printf("Serving mutating admission webhook on %s", whsvr.Server.Addr) 63 | 64 | var startServer func() error 65 | if parameters.NoHTTPS { 66 | startServer = func() error { 67 | return whsvr.Server.ListenAndServe() 68 | } 69 | } else { 70 | startServer = func() error { 71 | return whsvr.Server.ListenAndServeTLS( 72 | parameters.CertFile, 73 | parameters.KeyFile, 74 | ) 75 | } 76 | } 77 | 78 | if err := startServer(); err != nil { 79 | log.Printf("Failed to listen and serve: %v", err) 80 | os.Exit(1) 81 | } 82 | }() 83 | 84 | // listen for OS shutdown signal 85 | signalChan := make(chan os.Signal, 1) 86 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 87 | <-signalChan 88 | 89 | log.Printf("Received OS shutdown signal, shutting down webhook server gracefully...") 90 | whsvr.Server.Shutdown(context.Background()) 91 | } 92 | -------------------------------------------------------------------------------- /deployment/crd.yaml: -------------------------------------------------------------------------------- 1 | # Secretless Broker creates this CRD on startup, however if you wish to configure 2 | # SB via a CRD, then the CRD has to exist prior to SB starting. 3 | 4 | --- 5 | apiVersion: apiextensions.k8s.io/v1 6 | kind: CustomResourceDefinition 7 | metadata: 8 | name: configurations.secretless.io 9 | spec: 10 | group: secretless.io 11 | names: 12 | kind: Configuration 13 | plural: configurations 14 | singular: configuration 15 | shortNames: 16 | - sbconfig 17 | scope: Namespaced 18 | versions: 19 | - name: v1 20 | served: true 21 | storage: true 22 | schema: 23 | openAPIV3Schema: 24 | type: object 25 | properties: 26 | spec: 27 | type: object 28 | properties: 29 | listeners: 30 | type: array 31 | items: 32 | type: object 33 | properties: 34 | name: 35 | type: string 36 | protocol: 37 | type: string 38 | socket: 39 | type: string 40 | address: 41 | type: string 42 | debug: 43 | type: boolean 44 | caCertFiles: 45 | type: array 46 | items: 47 | type: string 48 | handlers: 49 | type: array 50 | items: 51 | type: object 52 | properties: 53 | name: 54 | type: string 55 | type: 56 | type: string 57 | listener: 58 | type: string 59 | debug: 60 | type: boolean 61 | match: 62 | type: array 63 | items: 64 | type: string 65 | credentials: 66 | type: array 67 | items: 68 | type: object 69 | properties: 70 | name: 71 | type: string 72 | provider: 73 | type: string 74 | id: 75 | type: string 76 | -------------------------------------------------------------------------------- /deployment/deployment.yaml.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | defaultSidecarInjectorImage="cyberark/sidecar-injector:latest" 6 | 7 | usage() { 8 | cat <> ${tmpdir}/csr.conf 71 | [req] 72 | req_extensions = v3_req 73 | distinguished_name = req_distinguished_name 74 | [req_distinguished_name] 75 | [ v3_req ] 76 | basicConstraints = CA:FALSE 77 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 78 | extendedKeyUsage = serverAuth 79 | subjectAltName = @alt_names 80 | [alt_names] 81 | DNS.1 = ${service} 82 | DNS.2 = ${service}.${namespace} 83 | DNS.3 = ${service}.${namespace}.svc 84 | EOF 85 | 86 | openssl genrsa -out ${tmpdir}/server-key.pem 2048 87 | openssl req -new -key ${tmpdir}/server-key.pem -subj "/CN=system:node:${service}.${namespace}.svc/O=system:nodes" -out ${tmpdir}/server.csr -config ${tmpdir}/csr.conf 88 | 89 | # clean-up any previously created CSR for our service. Ignore errors if not present. 90 | kubectl delete csr "${csrName}" 2>/dev/null || true 91 | 92 | # create server cert/key CSR and send to k8s API 93 | cat <&2 129 | exit 1 130 | fi 131 | echo ${serverCert} | openssl base64 -d -A -out ${tmpdir}/server-cert.pem 132 | 133 | 134 | # create the secret with CA cert and server cert/key 135 | kubectl create secret generic ${secret} \ 136 | --from-file=key.pem=${tmpdir}/server-key.pem \ 137 | --from-file=cert.pem=${tmpdir}/server-cert.pem \ 138 | --dry-run=client -o yaml | 139 | kubectl -n ${namespace} apply -f - 140 | -------------------------------------------------------------------------------- /deployment/webhook-patch-ca-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | usage() { 6 | cat <&2 7 | Generate MutatingWebhookConfiguration for CyberArk sidecar injector webhook service. 8 | 9 | This script uses generates a MutatingWebhookConfiguration using the provided service name of the webhook and the namespace where the webhook service resides. 10 | 11 | usage: ${0} [OPTIONS] 12 | 13 | The following flags are required. 14 | 15 | --service Service name of webhook. 16 | --namespace Namespace where webhook service resides. 17 | --namespace-selector-label Label which should be set to "enabled" for namespace to use Sidecar Injector. 18 | EOF 19 | exit 1 20 | } 21 | 22 | while [[ $# -gt 0 ]]; do 23 | case ${1} in 24 | --namespace-selector-label) 25 | namespaceSelectorLabel="$2" 26 | shift 27 | ;; 28 | --service) 29 | service="$2" 30 | shift 31 | ;; 32 | --namespace) 33 | namespace="$2" 34 | shift 35 | ;; 36 | *) 37 | usage 38 | ;; 39 | esac 40 | shift 41 | done 42 | 43 | if [ -z ${service} ] || [ -z ${namespace} ] || [ -z ${namespaceSelectorLabel} ] 44 | then 45 | usage 46 | fi 47 | 48 | ROOT=$(cd $(dirname $0)/../../; pwd) 49 | 50 | set -o errexit 51 | set -o nounset 52 | set -o pipefail 53 | 54 | export CA_BUNDLE=$(kubectl get configmap -n kube-system extension-apiserver-authentication -o=jsonpath='{.data.client-ca-file}' | base64 | tr -d '\n') 55 | 56 | if command -v envsubst >/dev/null 2>&1; then 57 | envsubst 58 | else 59 | sed \ 60 | -e "s|\${CA_BUNDLE}|${CA_BUNDLE}|g" \ 61 | -e "s|\${namespaceSelectorLabel}|${namespaceSelectorLabel}|g" \ 62 | -e "s|\${namespace}|${namespace}|g" \ 63 | -e "s|\${service}|${service}|g" 64 | fi 65 | -------------------------------------------------------------------------------- /design/admission-controller-phases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyberark/sidecar-injector/f01562b66e96ec74fe91ce49a4f57e86ba6fe449/design/admission-controller-phases.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cyberark/sidecar-injector 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/evanphx/json-patch v5.6.0+incompatible 7 | github.com/stretchr/testify v1.8.1 8 | k8s.io/api v0.27.3 9 | k8s.io/apimachinery v0.27.3 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/go-logr/logr v1.2.3 // indirect 15 | github.com/gogo/protobuf v1.3.2 // indirect 16 | github.com/google/gofuzz v1.2.0 // indirect 17 | github.com/json-iterator/go v1.1.12 // indirect 18 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 19 | github.com/modern-go/reflect2 v1.0.2 // indirect 20 | github.com/pkg/errors v0.9.1 // indirect 21 | github.com/pmezard/go-difflib v1.0.0 // indirect 22 | golang.org/x/net v0.17.0 // indirect 23 | golang.org/x/text v0.13.0 // indirect 24 | gopkg.in/inf.v0 v0.9.1 // indirect 25 | gopkg.in/yaml.v2 v2.4.0 // indirect 26 | gopkg.in/yaml.v3 v3.0.1 // indirect 27 | k8s.io/klog/v2 v2.90.1 // indirect 28 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect 29 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 30 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 31 | sigs.k8s.io/yaml v1.3.0 // indirect 32 | ) 33 | 34 | replace golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 => golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b 35 | 36 | replace golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 => golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b 37 | 38 | replace golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 => golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b 39 | 40 | replace golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 => golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b 41 | 42 | replace golang.org/x/net v0.0.0-20180724234803-3673e40ba225 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 43 | 44 | replace golang.org/x/net v0.0.0-20180826012351-8a410e7b638d => golang.org/x/net v0.0.0-20220923203811-8be639271d50 45 | 46 | replace golang.org/x/net v0.0.0-20180906233101-161cd47e91fd => golang.org/x/net v0.0.0-20220923203811-8be639271d50 47 | 48 | replace golang.org/x/net v0.0.0-20190213061140-3a22650c66bd => golang.org/x/net v0.0.0-20220923203811-8be639271d50 49 | 50 | replace golang.org/x/net v0.0.0-20190311183353-d8887717615a => golang.org/x/net v0.0.0-20220923203811-8be639271d50 51 | 52 | replace golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 53 | 54 | replace golang.org/x/net v0.0.0-20190620200207-3b0461eec859 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 55 | 56 | replace golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 57 | 58 | replace golang.org/x/net v0.0.0-20200226121028-0de0cce0169b => golang.org/x/net v0.0.0-20220923203811-8be639271d50 59 | 60 | replace golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 61 | 62 | replace golang.org/x/net v0.0.0-20201021035429-f5854403a974 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 63 | 64 | replace golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 65 | 66 | replace golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 67 | 68 | replace golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 69 | 70 | replace golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f => golang.org/x/net v0.0.0-20220923203811-8be639271d50 71 | 72 | replace golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 73 | 74 | replace golang.org/x/net v0.0.0-20211209124913-491a49abca63 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 75 | 76 | replace golang.org/x/net v0.0.0-20220225172249-27dd8689420f => golang.org/x/net v0.0.0-20220923203811-8be639271d50 77 | 78 | replace golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 => golang.org/x/net v0.0.0-20220923203811-8be639271d50 79 | 80 | replace golang.org/x/net v0.0.0-20220722155237-a158d28d115b => golang.org/x/net v0.0.0-20220923203811-8be639271d50 81 | 82 | replace golang.org/x/net v0.0.0-20220923203811-8be639271d50 => golang.org/x/net v0.4.0 83 | 84 | replace golang.org/x/text v0.3.0 => golang.org/x/text v0.3.8 85 | 86 | replace golang.org/x/text v0.3.2 => golang.org/x/text v0.3.8 87 | 88 | replace golang.org/x/text v0.3.3 => golang.org/x/text v0.3.8 89 | 90 | replace golang.org/x/text v0.3.5 => golang.org/x/text v0.3.8 91 | 92 | replace golang.org/x/text v0.3.6 => golang.org/x/text v0.3.8 93 | 94 | replace golang.org/x/text v0.3.7 => golang.org/x/text v0.3.8 95 | 96 | replace github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 => github.com/emicklei/go-restful/v3 v3.8.0 97 | 98 | replace gopkg.in/yaml.v2 v2.2.1 => gopkg.in/yaml.v2 v2.2.8 99 | 100 | replace gopkg.in/yaml.v2 v2.2.2 => gopkg.in/yaml.v2 v2.2.8 101 | 102 | replace gopkg.in/yaml.v2 v2.2.4 => gopkg.in/yaml.v2 v2.2.8 103 | 104 | replace gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c => gopkg.in/yaml.v3 v3.0.1 105 | 106 | replace gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 => gopkg.in/yaml.v3 v3.0.1 107 | 108 | replace gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b => gopkg.in/yaml.v3 v3.0.1 109 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= 5 | github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 6 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 7 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 8 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 9 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 10 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 11 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 12 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 13 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 14 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 15 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 16 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 17 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 18 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 19 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 20 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 21 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 22 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 23 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 24 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 25 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 26 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 27 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 28 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 30 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 31 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 34 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 35 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 36 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 37 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 38 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 39 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 40 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 41 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 42 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 43 | golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 44 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 45 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 46 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 47 | golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 48 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 49 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 50 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 51 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 52 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 53 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 54 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 55 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 59 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 60 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 61 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 62 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 63 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 64 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 65 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 66 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 67 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 68 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 69 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 70 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 71 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 72 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 73 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 74 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 75 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 76 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 77 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 78 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 79 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 80 | k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y= 81 | k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg= 82 | k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= 83 | k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= 84 | k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= 85 | k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 86 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= 87 | k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 88 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 89 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 90 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 91 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 92 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 93 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 94 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: A Helm chart for deploying CyberArk sidecar injector mutating admission webhook 3 | name: cyberark-sidecar-injector 4 | version: 0.2.0-alpha 5 | home: https://github.com/cyberark/sidecar-injector 6 | icon: https://www.cyberark.com/wp-content/uploads/2015/12/cybr-aim.jpg 7 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/README.md: -------------------------------------------------------------------------------- 1 | # cyberark-sidecar-injector 2 | 3 | CyberArk Sidecar Injector is a [MutatingAdmissionWebhook](https://kubernetes.io/docs/admin/admission-controllers/#mutatingadmissionwebhook-beta-in-19) server which allows for configurable sidecar injection into a pod prior to persistence. 4 | 5 | * [TL;DR;](#tl-dr-) 6 | * [Introduction](#introduction) 7 | * [Prerequisites](#prerequisites) 8 | + [Mandatory TLS](#mandatory-tls) 9 | * [Installing the Chart](#installing-the-chart) 10 | * [Uninstalling the Chart](#uninstalling-the-chart) 11 | * [Configuration](#configuration) 12 | + [csrEnabled=true](#csrenabledtrue) 13 | + [certsSecret](#certssecret) 14 | 15 | ## TL;DR; 16 | 17 | ```bash 18 | $ helm install -f values.yaml my-release . 19 | ``` 20 | 21 | ## Introduction 22 | 23 | This chart bootstraps a deployment of a CyberArk Sidecar Injector MutatingAdmissionWebhook server including the Service and MutatingWebhookConfiguration. 24 | 25 | ## Prerequisites 26 | 27 | - Kubernetes 1.4+ with Beta APIs enabled 28 | 29 | ### Mandatory TLS 30 | 31 | Supporting TLS for external webhook server is required because admission is a high security operation. As part of the installation process, we need to create a TLS certificate signed by a trusted CA (shown below is the Kubernetes CA but you can use your own) to secure the communication between the webhook server and apiserver. For the complete steps of creating and approving Certificate Signing Requests(CSR), please refer to [Managing TLS in a cluster](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/). 32 | 33 | ## Installing the Chart 34 | 35 | To install the chart with the release name `my-release`, follow the instructions in the NOTES section on how to approve the CSR: 36 | 37 | ```bash 38 | $ helm install \ 39 | --set caBundle="$(kubectl -n kube-system \ 40 | get configmap \ 41 | extension-apiserver-authentication \ 42 | -o=jsonpath='{.data.client-ca-file}' \ 43 | )" \ 44 | my-release \ --generate-name 45 | . 46 | ``` 47 | 48 | ``` 49 | ... 50 | 51 | NOTES: 52 | ## Instructions 53 | Before you can proceed to use the sidecar-injector, there's one last step. 54 | You will need to approve the CSR (Certificate Signing Request) made by the sidecar-injector. 55 | This allows the sidecar-injector to communicate securely with the Kubernetes API. 56 | 57 | ### Watch initContainer logs for when CSR is created 58 | kubectl -n injectors logs deployment/cyberark-sidecar-injector -c init-webhook -f 59 | 60 | ### You can check and inspect the CSR 61 | kubectl describe csr "cyberark-sidecar-injector.injectors" 62 | 63 | ### Approve the CSR 64 | kubectl certificate approve "cyberark-sidecar-injector.injectors" 65 | 66 | Now that everything is setup you can enjoy the Cyberark Sidecar Injector. 67 | This is the general workflow: 68 | 69 | 1. Annotate your application to enable the injector and to configure the sidecar (see README.md) 70 | 2. Webhook intercepts and injects containers as needed 71 | 72 | Enjoy. 73 | 74 | ``` 75 | 76 | The command deploys the CyberArk Broker Sidecar Injector MutatingAdmissionWebhook on the Kubernetes cluster in the default configuration. In this configuration the chart uses the cluster CA certificate bundle with a Certificate Signing Request flow to allow TLS between the webhook server and the cluster. The caBundle is required. The [configuration](#configuration) section lists the parameters that can be configured during installation. 77 | 78 | > **Tip**: List all releases using `helm list` 79 | 80 | ## Uninstalling the Chart 81 | 82 | To uninstall/delete the `my-release` deployment: 83 | 84 | ```bash 85 | $ helm delete my-release 86 | ``` 87 | 88 | The command removes all the Kubernetes components associated with the chart and deletes the release. 89 | 90 | ## Configuration 91 | 92 | The following table lists the configurable parameters of the CyberArk Sidecar Injector chart and their default values. 93 | 94 | | Parameter | Description | Default| 95 | | --- | --- | --- | 96 | | `namespaceSelectorLabel` | Label which should be set to "enabled" for namespace to use Sidecar Injector | `cyberark-sidecar-injector` (required) | 97 | | `caBundle`| CA certificate bundle that signs the server cert used by the webhook | `nil` (required) | 98 | | `csrEnabled` | Generate a private key and certificate signing request towards the Kubernetes Cluster | `true` | 99 | | `certsSecret` | Private key and signed certificate used by the webhook server | `nil` (required if csrEnabled is false) | 100 | | `sidecarInjectorImage` | Container image for the sidecar injector. | `cyberark/sidecar-injector:latest` | 101 | | `secretlessImage` | Container image for the Secretless sidecar. | `cyberark/secretless-broker:latest` | 102 | | `authenticatorImage` | Container image for the Kubernetes Authenticator sidecar. | `cyberark/conjur-kubernetes-authenticator:latest` | 103 | | `secretsProviderImage` | Container image for the Secrets Provider sidecar. | `cyberark/secrets-provider-for-k8s:latest` | 104 | | `deploymentApiVersion` | The supported apiVersion for Deployments. This is the value that will be set in the Deployment manifest. It defaults to the supported apiVersion for Deployments on the latest Kubernetes release. | `apps/v1` | 105 | 106 | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, 107 | 108 | ```bash 109 | $ helm install \ 110 | --set csrEnabled="false" \ 111 | --set certsSecret="some-secret" \ 112 | --set caBundle="-----BEGIN CERTIFICATE-----..." \ 113 | my-release \ 114 | . 115 | ``` 116 | 117 | The above command creates a sidecar injector deployment, retrieves the private key and signed certificate from the `certsSecret` value and uses the `caBundle` value in the associated MutatingWebhookConfiguration. Note that `caBundle` is the certificate that signs the injector webhook server cert. 118 | 119 | Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, 120 | 121 | ```bash 122 | $ helm install -f values.yaml my-release . 123 | ``` 124 | 125 | ### certsSecret 126 | 127 | `certsSecret` is a Kubernetes Secret containing private key and signed certificate (on paths key.pem and cert.pem, respectively) 128 | used by the webhook server. 129 | 130 | It is required for the private key and signed certificate pair to contain entries for the DNS name of the webhook service, i.e., ..svc, or the URL of the webhook server. 131 | 132 | ### caBundle 133 | 134 | `caBundle` is the **required** CA certificate bundle that signs the server cert used by the webhook server. It is used in the MutatingWebhookConfiguration for the release. 135 | 136 | ### csrEnabled 137 | 138 | When `csrEnabled` is set to `true`, the chart generate a private key and certificate signing request (CSR) towards the Kubernetes Cluster, and waits until the CSR is approved before deploying the sidecar injector. 139 | 140 | The private key and certificate will be stored in a secret created as part of the release. 141 | 142 | The `caBundle` in this case is the Kubernetes cluster CA certificate. This can be retrieve as follows: 143 | 144 | ``` 145 | kubectl -n kube-system \ 146 | get configmap \ 147 | extension-apiserver-authentication \ 148 | -o=jsonpath='{.data.client-ca-file}' 149 | ``` 150 | 151 | ### sidecarInjectorImage 152 | 153 | `sidecarInjectorImage` is the container image for the sidecar injector. 154 | 155 | ### secretlessImage 156 | 157 | `secretlessImage` is the container image for the Secretless sidecar. 158 | 159 | ### authenticatorImage 160 | 161 | `authenticatorImage` is the container image for the Kubernetes Authenticator sidecar. 162 | 163 | ### secretsProviderImage 164 | 165 | `secretsProviderImage` is the container image for the Secrets Provider sidecar. 166 | 167 | ### deploymentApiVersion 168 | 169 | `deploymentApiVersion` is the supported apiVersion for Deployments. This is the value that 170 | will be set in the Deployment manifest. 171 | 172 | `deploymentApiVersion` defaults to the supported apiVersion for Deployments on the latest 173 | Kubernetes release. 174 | 175 | If you're on an older version of Kubernetes, you can retrieve the supported apiVersion 176 | for Deployments by running the shell commands below. Update deploymentApiVersion to the 177 | returned. 178 | 179 | ```bash 180 | deploymentApiGroup=$(kubectl api-resources | \ 181 | grep "deployments.*deploy.*Deployment" | awk '{print $3}') 182 | 183 | deploymentApiVersion=$(kubectl api-versions | grep "${deploymentApiGroup}/") 184 | 185 | echo ${deploymentApiVersion} 186 | ``` 187 | 188 | # 189 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | ## Instructions 2 | 3 | {{- if .Values.csrEnabled }} 4 | Before you can proceed to use the sidecar-injector, there's one last step. 5 | You will need to approve the CSR (Certificate Signing Request) made by the sidecar-injector. 6 | This allows the sidecar-injector to communicate securely with the Kubernetes API. 7 | 8 | ### Watch initContainer logs for when CSR is created 9 | kubectl -n {{ .Release.Namespace }} logs deployment/{{ include "cyberark-sidecar-injector.name" . }} -c init-webhook -f 10 | 11 | ### You can check and inspect the CSR 12 | kubectl describe csr "{{ include "cyberark-sidecar-injector.name" . }}.{{ .Release.Namespace }}" 13 | 14 | ### Approve the CSR 15 | kubectl certificate approve "{{ include "cyberark-sidecar-injector.name" . }}.{{ .Release.Namespace }}" 16 | {{- end }} 17 | 18 | Now that everything is setup you can enjoy the Cyberark Sidecar Injector. 19 | This is the general workflow: 20 | 21 | 1. Annotate your application to enable the injector and to configure the sidecar (see README.md) 22 | 2. Webhook intercepts and injects containers as needed 23 | 24 | Enjoy. 25 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "cyberark-sidecar-injector.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "cyberark-sidecar-injector.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "cyberark-sidecar-injector.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/crd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: configurations.secretless{{ .Values.SECRETLESS_CRD_SUFFIX }}.io 6 | spec: 7 | group: secretless{{ .Values.SECRETLESS_CRD_SUFFIX }}.io 8 | names: 9 | kind: Configuration 10 | plural: configurations 11 | singular: configuration 12 | shortNames: 13 | - sbconfig 14 | scope: Namespaced 15 | versions: 16 | - name: v1 17 | served: true 18 | storage: true 19 | schema: 20 | openAPIV3Schema: 21 | type: object 22 | properties: 23 | spec: 24 | type: object 25 | properties: 26 | listeners: 27 | type: array 28 | items: 29 | type: object 30 | properties: 31 | name: 32 | type: string 33 | protocol: 34 | type: string 35 | socket: 36 | type: string 37 | address: 38 | type: string 39 | debug: 40 | type: boolean 41 | caCertFiles: 42 | type: array 43 | items: 44 | type: string 45 | handlers: 46 | type: array 47 | items: 48 | type: object 49 | properties: 50 | name: 51 | type: string 52 | type: 53 | type: string 54 | listener: 55 | type: string 56 | debug: 57 | type: boolean 58 | match: 59 | type: array 60 | items: 61 | type: string 62 | credentials: 63 | type: array 64 | items: 65 | type: object 66 | properties: 67 | name: 68 | type: string 69 | provider: 70 | type: string 71 | id: 72 | type: string 73 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: {{ .Values.deploymentApiVersion }} 3 | kind: Deployment 4 | metadata: 5 | name: {{ include "cyberark-sidecar-injector.name" . }} 6 | labels: 7 | app: {{ include "cyberark-sidecar-injector.name" . }} 8 | chart: {{ include "cyberark-sidecar-injector.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | spec: 12 | replicas: {{ .Values.replicaCount }} 13 | selector: 14 | matchLabels: 15 | app: {{ include "cyberark-sidecar-injector.name" . }} 16 | release: {{ .Release.Name }} 17 | template: 18 | metadata: 19 | labels: 20 | app: {{ include "cyberark-sidecar-injector.name" . }} 21 | release: {{ .Release.Name }} 22 | spec: 23 | serviceAccountName: {{ include "cyberark-sidecar-injector.name" . }} 24 | {{- if .Values.csrEnabled }} 25 | initContainers: 26 | - name: init-webhook 27 | image: {{ .Values.sidecarInjectorImage }} 28 | imagePullPolicy: {{ .Values.sidecarInjectorImagePullPolicy }} 29 | command: 30 | - /bin/sh 31 | - -c 32 | args: 33 | - | 34 | set -e 35 | 36 | secret={{ include "cyberark-sidecar-injector.name" . | quote }} 37 | service={{ include "cyberark-sidecar-injector.name" . | quote }} 38 | namespace={{ .Release.Namespace | quote }} 39 | 40 | csrName=${service}.${namespace} 41 | tmpdir=$(mktemp -d) 42 | echo "creating certs in tmpdir ${tmpdir} " 43 | 44 | cat <> ${tmpdir}/csr.conf 45 | [req] 46 | req_extensions = v3_req 47 | distinguished_name = req_distinguished_name 48 | [req_distinguished_name] 49 | [ v3_req ] 50 | basicConstraints = CA:FALSE 51 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 52 | extendedKeyUsage = serverAuth 53 | subjectAltName = @alt_names 54 | [alt_names] 55 | DNS.1 = ${service} 56 | DNS.2 = ${service}.${namespace} 57 | DNS.3 = ${service}.${namespace}.svc 58 | EOF 59 | 60 | openssl genrsa -out ${tmpdir}/server-key.pem 2048 61 | openssl req -new -key ${tmpdir}/server-key.pem -subj "/CN=system:node:${service}.${namespace}.svc/O=system:nodes" -out ${tmpdir}/server.csr -config ${tmpdir}/csr.conf 62 | 63 | echo "Cleaning up any previously created CSR for sidecar injector" 64 | kubectl delete csr "${csrName}" --wait=true --ignore-not-found=true 65 | 66 | echo "Checking available Kubernetes API versions." 67 | kubectl version 68 | kubectl api-versions | grep certificates.k8s.io 69 | kubectl api-versions | grep rbac.authorization.k8s.io 70 | kubectl api-versions | grep admissionregistration.k8s.io 71 | 72 | csrAPIResources="$(kubectl api-resources \ 73 | | grep CertificateSigningRequest \ 74 | | head -1 | awk '{print $3}')" 75 | csrAPIVersion="$(kubectl api-versions | grep "${csrAPIResources}" | head -n 1)" 76 | echo "Creating server cert/key CSR and sending to k8s API controller" 77 | cat <&2 120 | exit 1 121 | fi 122 | echo ${serverCert} | openssl base64 -d -A -out ${tmpdir}/server-cert.pem 123 | 124 | # create the secret with CA cert and server cert/key 125 | kubectl create secret generic ${secret} \ 126 | --from-file=key.pem=${tmpdir}/server-key.pem \ 127 | --from-file=cert.pem=${tmpdir}/server-cert.pem \ 128 | --dry-run -o yaml | 129 | kubectl -n ${namespace} apply -f - 130 | echo "sidecar inject certs setup successful." 131 | {{- end }} 132 | containers: 133 | - name: cyberark-sidecar-injector 134 | image: {{ .Values.sidecarInjectorImage }} 135 | imagePullPolicy: {{ .Values.sidecarInjectorImagePullPolicy }} 136 | args: 137 | - -tlsCertFile=/etc/webhook/certs/cert.pem 138 | - -tlsKeyFile=/etc/webhook/certs/key.pem 139 | - -port=8080 140 | - -secretless-image={{ .Values.secretlessImage }} 141 | - -authenticator-image={{ .Values.authenticatorImage }} 142 | - -secrets-provider-image={{ .Values.secretsProviderImage }} 143 | env: 144 | - name: SECRETLESS_CRD_SUFFIX 145 | value: "{{ .Values.SECRETLESS_CRD_SUFFIX }}" 146 | envFrom: 147 | - configMapRef: 148 | name: {{ .Values.conjurConfig }} 149 | ports: 150 | - containerPort: 8080 151 | name: https 152 | volumeMounts: 153 | - name: webhook-certs 154 | mountPath: /etc/webhook/certs 155 | readOnly: true 156 | volumes: 157 | - name: webhook-certs 158 | secret: 159 | {{- if not .Values.csrEnabled }} 160 | secretName: {{ required "A valid .Values.certsSecret entry required!" .Values.certsSecret | quote }} 161 | {{- else }} 162 | secretName: {{ include "cyberark-sidecar-injector.name" . }} 163 | {{- end }} 164 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/entitlements.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Role 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: "{{ include "cyberark-sidecar-injector.name" . }}-certs-reader" 6 | rules: 7 | {{- if .Values.csrEnabled }} 8 | - apiGroups: [""] # "" indicates the core API group 9 | resources: ["secrets"] 10 | resourceNames: [{{ include "cyberark-sidecar-injector.name" . | quote }}] 11 | verbs: ["get", "patch"] 12 | {{- else }} 13 | - apiGroups: [""] # "" indicates the core API group 14 | resources: ["secrets"] 15 | resourceNames: [{{ include "cyberark-sidecar-injector.name" . | quote }}] 16 | verbs: ["get"] 17 | {{- end }} 18 | 19 | --- 20 | kind: RoleBinding 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | metadata: 23 | name: "{{ include "cyberark-sidecar-injector.name" . }}-certs-reader" 24 | subjects: 25 | - kind: ServiceAccount 26 | name: "{{ include "cyberark-sidecar-injector.name" . }}" 27 | roleRef: 28 | kind: Role 29 | name: "{{ include "cyberark-sidecar-injector.name" . }}-certs-reader" 30 | apiGroup: rbac.authorization.k8s.io 31 | 32 | {{- if .Values.csrEnabled }} 33 | --- 34 | apiVersion: rbac.authorization.k8s.io/v1 35 | kind: ClusterRole 36 | metadata: 37 | name: "{{ include "cyberark-sidecar-injector.name" . }}.{{ .Release.Namespace }}" 38 | rules: 39 | - apiGroups: ["certificates.k8s.io"] 40 | resources: ["certificatesigningrequests"] 41 | resourceNames: ["{{ include "cyberark-sidecar-injector.name" . }}.{{ .Release.Namespace }}"] 42 | verbs: ["get", "watch", "delete"] 43 | - apiGroups: ["certificates.k8s.io"] 44 | resources: ["certificatesigningrequests"] 45 | verbs: ["create"] 46 | 47 | --- 48 | apiVersion: rbac.authorization.k8s.io/v1 49 | kind: ClusterRoleBinding 50 | metadata: 51 | name: "{{ include "cyberark-sidecar-injector.name" . }}.{{ .Release.Namespace }}" 52 | roleRef: 53 | apiGroup: rbac.authorization.k8s.io 54 | kind: ClusterRole 55 | name: "{{ include "cyberark-sidecar-injector.name" . }}.{{ .Release.Namespace }}" 56 | subjects: 57 | - kind: ServiceAccount 58 | name: "{{ include "cyberark-sidecar-injector.name" . }}" 59 | namespace: {{ .Release.Namespace | quote }} 60 | {{- end }} 61 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/mutatingwebhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | name: {{ include "cyberark-sidecar-injector.name" . }}.{{ .Release.Namespace }} 5 | labels: 6 | app: {{ include "cyberark-sidecar-injector.name" . }} 7 | chart: {{ include "cyberark-sidecar-injector.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | webhooks: 11 | - name: sidecar-injector.conjur.org 12 | clientConfig: 13 | service: 14 | name: {{ include "cyberark-sidecar-injector.name" . | quote }} 15 | namespace: {{ .Release.Namespace | quote }} 16 | path: "/mutate" 17 | caBundle: {{ (required "A valid .Values.caBundle entry required!" .Values.caBundle) | b64enc | quote }} 18 | rules: 19 | - operations: [ "CREATE" ] 20 | apiGroups: [""] 21 | apiVersions: ["v1"] 22 | resources: ["pods"] 23 | admissionReviewVersions: ["v1"] 24 | sideEffects: None 25 | namespaceSelector: 26 | matchLabels: 27 | {{ (required "A valid .Values.namespaceSelectorLabel entry required!" .Values.namespaceSelectorLabel) }}: enabled 28 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Secret 3 | apiVersion: v1 4 | metadata: 5 | name: "{{ include "cyberark-sidecar-injector.name" . }}" 6 | type: Opaque 7 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "cyberark-sidecar-injector.name" . }} 5 | labels: 6 | app: {{ include "cyberark-sidecar-injector.name" . }} 7 | chart: {{ include "cyberark-sidecar-injector.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | ports: 12 | - port: 443 13 | targetPort: https 14 | protocol: TCP 15 | name: https 16 | selector: 17 | app: {{ include "cyberark-sidecar-injector.name" . }} 18 | release: {{ .Release.Name }} 19 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/service_account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This role is allow to [get] the credentials secret 3 | # in the namespace where this manifest is applied 4 | kind: ServiceAccount 5 | apiVersion: v1 6 | metadata: 7 | name: "{{ include "cyberark-sidecar-injector.name" . }}" 8 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/templates/serviceaccountsecret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | type: kubernetes.io/service-account-token 4 | metadata: 5 | name: {{ include "cyberark-sidecar-injector.name" . }}-service-account-token 6 | labels: 7 | release: {{ .Release.Name }} 8 | heritage: {{ .Release.Service }} 9 | helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} 10 | annotations: 11 | kubernetes.io/service-account.name: {{ include "cyberark-sidecar-injector.name" . }} 12 | -------------------------------------------------------------------------------- /helm/cyberark-sidecar-injector/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for cyberark-sidecar-injector. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | # helm install --namespace default meow . 6 | # 7 | 8 | replicaCount: 1 9 | 10 | nameOverride: "" 11 | fullnameOverride: "" 12 | 13 | # caBundle: 14 | csrEnabled: true 15 | namespaceSelectorLabel: cyberark-sidecar-injector 16 | # certsSecret: 17 | 18 | secretlessImage: cyberark/secretless-broker:latest 19 | authenticatorImage: cyberark/conjur-kubernetes-authenticator:latest 20 | secretsProviderImage: cyberark/secrets-provider-for-k8s:latest 21 | 22 | sidecarInjectorImage: cyberark/sidecar-injector:latest 23 | SECRETLESS_CRD_SUFFIX: "" 24 | conjurConfig: conjur-configmap 25 | 26 | # deploymentApiVersion is the supported apiVersion for Deployments. This is the value that 27 | # will be set in the Deployment manifest. 28 | # 29 | # deploymentApiVersion defaults to the supported apiVersion for Deployments on the latest 30 | # Kubernetes release. 31 | # 32 | # If you're on an older version of Kubernetes, you can retrieve the supported apiVersion 33 | # for Deployments by running the shell commands below. Update deploymentApiVersion to the 34 | # returned. 35 | # 36 | # deploymentApiGroup=$(kubectl api-resources | \ 37 | # grep "deployments.*deploy.*Deployment" | awk '{print $3}') 38 | # 39 | # deploymentApiVersion=$(kubectl api-versions | grep "${deploymentApiGroup}/") 40 | # 41 | # echo ${deploymentApiVersion} 42 | # 43 | deploymentApiVersion: apps/v1 44 | -------------------------------------------------------------------------------- /pkg/inject/authenticator.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "sort" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | ) 8 | 9 | type AuthenticatorSidecarConfig struct { 10 | conjurConnConfigMapName string 11 | conjurAuthConfigMapName string 12 | containerMode string 13 | containerName string 14 | sidecarImage string 15 | } 16 | 17 | func (authConfig AuthenticatorSidecarConfig) ContainerNameOrDefault() string { 18 | name := "authenticator" 19 | if authConfig.containerName != "" { 20 | name = authConfig.containerName 21 | } 22 | 23 | return name 24 | } 25 | 26 | // generateAuthenticatorSidecarConfig generates PatchConfig from a 27 | // given AuthenticatorSidecarConfig 28 | func generateAuthenticatorSidecarConfig( 29 | authConfig AuthenticatorSidecarConfig, 30 | ) *PatchConfig { 31 | var containers, initContainers []corev1.Container 32 | 33 | authenticatorContainer := corev1.Container{ 34 | Name: authConfig.ContainerNameOrDefault(), 35 | Image: authConfig.sidecarImage, 36 | ImagePullPolicy: "Always", 37 | Env: []corev1.EnvVar{ 38 | envVarFromConfigMap( 39 | "CONJUR_ACCOUNT", 40 | authConfig.conjurConnConfigMapName, 41 | ), 42 | envVarFromConfigMap( 43 | "CONJUR_APPLIANCE_URL", 44 | authConfig.conjurConnConfigMapName, 45 | ), 46 | envVarFromConfigMap( 47 | "CONJUR_AUTHN_LOGIN", 48 | authConfig.conjurAuthConfigMapName, 49 | ), 50 | { 51 | Name: "CONJUR_AUTHN_TOKEN_FILE", 52 | Value: "/run/conjur/conjur-access-token", 53 | }, 54 | envVarFromConfigMap( 55 | "CONJUR_AUTHN_URL", 56 | authConfig.conjurConnConfigMapName, 57 | ), 58 | envVarFromConfigMap( 59 | "CONJUR_SSL_CERTIFICATE", 60 | authConfig.conjurConnConfigMapName, 61 | ), 62 | envVarFromConfigMap( 63 | "CONJUR_VERSION", 64 | authConfig.conjurConnConfigMapName, 65 | ), 66 | { 67 | Name: "CONTAINER_MODE", 68 | Value: authConfig.containerMode, 69 | }, 70 | envVarFromFieldPath( 71 | "MY_POD_IP", 72 | "status.podIP", 73 | ), 74 | envVarFromFieldPath( 75 | "MY_POD_NAME", 76 | "metadata.name", 77 | ), 78 | envVarFromFieldPath( 79 | "MY_POD_NAMESPACE", 80 | "metadata.namespace", 81 | ), 82 | }, 83 | VolumeMounts: []corev1.VolumeMount{ 84 | { 85 | Name: "conjur-access-token", 86 | MountPath: "/run/conjur", 87 | }, 88 | }, 89 | } 90 | 91 | // Sort envvars lexicographically 92 | sort.Slice(authenticatorContainer.Env, func(i, j int) bool { 93 | return authenticatorContainer.Env[i].Name < authenticatorContainer.Env[j].Name 94 | }) 95 | 96 | candidates := []corev1.Container{authenticatorContainer} 97 | if authConfig.containerMode == "init" { 98 | initContainers = candidates 99 | } else { 100 | containers = candidates 101 | } 102 | 103 | return &PatchConfig{ 104 | Containers: containers, 105 | InitContainers: initContainers, 106 | Volumes: []corev1.Volume{ 107 | { 108 | Name: "conjur-access-token", 109 | VolumeSource: corev1.VolumeSource{ 110 | EmptyDir: &corev1.EmptyDirVolumeSource{ 111 | Medium: "Memory", 112 | }, 113 | }, 114 | }, 115 | }, 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /pkg/inject/authenticator_test.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type injectionTestCase struct { 11 | description string 12 | annotatedPodTemplateSpecPath string 13 | expectedInjectedPodTemplateSpecPath string 14 | env map[string]string 15 | } 16 | 17 | func TestAuthenticatorSidecarInjection(t *testing.T) { 18 | var testCases = []injectionTestCase{ 19 | { 20 | description: "Kubernetes Authenticator", 21 | annotatedPodTemplateSpecPath: "./testdata/authenticator-annotated-pod.json", 22 | expectedInjectedPodTemplateSpecPath: "./testdata/authenticator-mutated-pod.json", 23 | }, 24 | { 25 | description: "Kubernetes Authenticator with image", 26 | annotatedPodTemplateSpecPath: "./testdata/authenticator-annotated-pod-with-image.json", 27 | expectedInjectedPodTemplateSpecPath: "./testdata/authenticator-mutated-pod-with-image.json", 28 | }, 29 | } 30 | 31 | for _, tc := range testCases { 32 | t.Run(tc.description, func(t *testing.T) { 33 | // Create the Admission Request (wrapped in an Admission Review) from the 34 | // annotated pod fixture. The goal is to use the annotations as a signal to 35 | // the sidecar-injector to mutate the Pod template spec. 36 | req, err := newTestAdmissionRequest( 37 | tc.annotatedPodTemplateSpecPath, 38 | ) 39 | if !assert.NoError(t, err) { 40 | return 41 | } 42 | 43 | // Read the expected Pod template spec fixture. 44 | expectedMod, err := ioutil.ReadFile( 45 | tc.expectedInjectedPodTemplateSpecPath, 46 | ) 47 | if !assert.NoError(t, err) { 48 | return 49 | } 50 | 51 | // Get the modified Pod template spec from the input. 52 | mod, err := applyPatchToAdmissionRequest(req) 53 | if !assert.NoError(t, err) { 54 | return 55 | } 56 | 57 | // Assert that the modified Pod template spec should equal the expected Pod 58 | // template spec. 59 | assert.JSONEq(t, string(expectedMod), string(mod)) 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pkg/inject/common.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strings" 8 | 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | func metaName(meta *metav1.ObjectMeta) string { 14 | name := meta.GenerateName 15 | if name == "" { 16 | name = meta.Name 17 | } 18 | 19 | return name 20 | } 21 | 22 | // mutationRequired determines if target resource requires mutation 23 | func mutationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool { 24 | // skip special Kubernetes system namespaces 25 | for _, namespace := range ignoredList { 26 | if metadata.Namespace == namespace { 27 | log.Printf( 28 | "Skip mutation for %v for it' in special namespace:%v", 29 | metaName(metadata), 30 | metadata.Namespace, 31 | ) 32 | return false 33 | } 34 | } 35 | 36 | injectedStatus, _ := getAnnotation(metadata, annotationStatusKey) 37 | 38 | // determine whether to perform mutation based on annotation for the target resource 39 | required := strings.ToLower(injectedStatus) != "injected" 40 | if required { 41 | injectValue, _ := getAnnotation(metadata, annotationInjectKey) 42 | switch strings.ToLower(injectValue) { 43 | case "y", "yes", "true", "on": 44 | required = true 45 | default: 46 | required = false 47 | } 48 | } 49 | 50 | log.Printf( 51 | "Mutation policy for %s/%s: injected status: %q required:%v", 52 | metaName(metadata), 53 | metadata.Name, 54 | injectedStatus, 55 | required, 56 | ) 57 | 58 | return required 59 | } 60 | 61 | func envVarFromConfigMap(envVarName, configMapName string) corev1.EnvVar { 62 | return corev1.EnvVar{ 63 | Name: envVarName, 64 | ValueFrom: &corev1.EnvVarSource{ 65 | 66 | ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ 67 | LocalObjectReference: corev1.LocalObjectReference{ 68 | Name: configMapName, 69 | }, 70 | Key: envVarName, 71 | }, 72 | }, 73 | } 74 | } 75 | 76 | func envVarFromFieldPath(envVarName, fieldPath string) corev1.EnvVar { 77 | return corev1.EnvVar{ 78 | Name: envVarName, 79 | ValueFrom: &corev1.EnvVarSource{ 80 | FieldRef: &corev1.ObjectFieldSelector{ 81 | FieldPath: fieldPath, 82 | }, 83 | }, 84 | } 85 | } 86 | 87 | func envVarFromLiteral(envVarName, value string) corev1.EnvVar { 88 | return corev1.EnvVar{ 89 | Name: envVarName, 90 | Value: value, 91 | } 92 | } 93 | 94 | func getAnnotation(metadata *metav1.ObjectMeta, key string) (string, error) { 95 | annotations := metadata.GetAnnotations() 96 | if annotations == nil { 97 | annotations = map[string]string{} 98 | } 99 | 100 | value, hasKey := annotations[key] 101 | if !hasKey { 102 | return "", fmt.Errorf("missing annotation %s", key) 103 | } 104 | return value, nil 105 | } 106 | 107 | /** 108 | * Given a pod, return the name of the volume that contains 109 | * the service account token. VolumeMounts are used instead 110 | * of volumes so that the mount path can be matched on. 111 | * Otherwise a name pattern match would be required and 112 | * that could have unexpected results. 113 | */ 114 | func getServiceAccountTokenVolumeName(pod *corev1.Pod) (string, error) { 115 | for _, container := range pod.Spec.Containers { 116 | for _, volumeMount := range container.VolumeMounts { 117 | if volumeMount.MountPath == "/var/run/secrets/kubernetes.io/serviceaccount" { 118 | return volumeMount.Name, nil 119 | } 120 | } 121 | } 122 | 123 | return "", errors.New("service account token volume mount not found") 124 | } 125 | -------------------------------------------------------------------------------- /pkg/inject/config.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | type ContainerVolumeMounts map[string][]corev1.VolumeMount 8 | 9 | type PatchConfig struct { 10 | InitContainers []corev1.Container `yaml:"initContainers"` 11 | Containers []corev1.Container `yaml:"containers"` 12 | Volumes []corev1.Volume `yaml:"volumes"` 13 | ContainerVolumeMounts ContainerVolumeMounts `yaml:"volumeMounts"` 14 | } 15 | -------------------------------------------------------------------------------- /pkg/inject/constants.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | const ( 4 | annotationConjurAuthConfigKey = "conjur.org/conjurAuthConfig" 5 | annotationConjurConnConfigKey = "conjur.org/conjurConnConfig" 6 | annotationContainerNameKey = "conjur.org/container-name" 7 | annotationContainerModeKey = "conjur.org/container-mode" 8 | annotationConjurInjectVolumesKey = "conjur.org/conjur-inject-volumes" 9 | annotationInjectKey = "conjur.org/inject" 10 | annotationInjectTypeKey = "conjur.org/inject-type" 11 | annotationSecretlessConfigKey = "conjur.org/secretless-config" 12 | annotationSecretlessCRDSuffixKey = "conjur.org/secretless-CRD-suffix" 13 | annotationStatusKey = "conjur.org/status" 14 | annotationContainerImageKey = "conjur.org/container-image" 15 | annotationSecretsDestinationKey = "conjur.org/secrets-destination" 16 | ) 17 | // These annotations are only used for sidecar injector and not passed on to the 18 | // injected container 19 | var sidecarInjectorAnnot = []string { 20 | annotationConjurAuthConfigKey, 21 | annotationConjurConnConfigKey, 22 | annotationContainerNameKey, 23 | annotationConjurInjectVolumesKey, 24 | annotationInjectKey, 25 | annotationInjectTypeKey, 26 | annotationSecretlessConfigKey, 27 | annotationSecretlessCRDSuffixKey, 28 | annotationContainerImageKey, 29 | } -------------------------------------------------------------------------------- /pkg/inject/patch.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | corev1 "k8s.io/api/core/v1" 8 | ) 9 | 10 | // RFC6902 JSON patches 11 | type rfc6902PatchOperation struct { 12 | Op string `json:"op"` 13 | Path string `json:"path"` 14 | Value interface{} `json:"value,omitempty"` 15 | } 16 | 17 | // RFC6902 JSON patch operations 18 | const ( 19 | patchOperationAdd = "add" 20 | patchOperationReplace = "replace" 21 | ) 22 | 23 | // create mutation patch for resources 24 | func createPatch( 25 | pod *corev1.Pod, 26 | sidecarConfig *PatchConfig, 27 | annotations map[string]string, 28 | ) ([]byte, error) { 29 | var patch []rfc6902PatchOperation 30 | 31 | patch = append( 32 | patch, 33 | addContainer( 34 | pod.Spec.InitContainers, 35 | sidecarConfig.InitContainers, 36 | "/spec/initContainers", 37 | )..., 38 | ) 39 | patch = append( 40 | patch, 41 | addContainer( 42 | pod.Spec.Containers, 43 | sidecarConfig.Containers, 44 | "/spec/containers", 45 | )..., 46 | ) 47 | patch = append( 48 | patch, 49 | addVolume( 50 | pod.Spec.Volumes, 51 | sidecarConfig.Volumes, "/spec/volumes", 52 | )..., 53 | ) 54 | patch = append( 55 | patch, 56 | updateAnnotation( 57 | pod.Annotations, 58 | annotations, 59 | )..., 60 | ) 61 | patch = append( 62 | patch, 63 | addVolumeMounts( 64 | pod.Spec.Containers, 65 | sidecarConfig.ContainerVolumeMounts, 66 | "/spec/containers", 67 | )..., 68 | ) 69 | 70 | return json.Marshal(patch) 71 | } 72 | 73 | // addContainer create a patch for adding containers 74 | func addContainer( 75 | target, added []corev1.Container, 76 | basePath string, 77 | ) (patch []rfc6902PatchOperation) { 78 | first := len(target) == 0 79 | var value interface{} 80 | 81 | for _, add := range added { 82 | value = add 83 | path := basePath 84 | if first { 85 | first = false 86 | value = []corev1.Container{add} 87 | } else { 88 | path = path + "/-" 89 | } 90 | patch = append(patch, rfc6902PatchOperation{ 91 | Op: patchOperationAdd, 92 | Path: path, 93 | Value: value, 94 | }) 95 | } 96 | 97 | return patch 98 | } 99 | 100 | // addVolumeMounts creates a patch for adding volume mounts 101 | func addVolumeMounts( 102 | target []corev1.Container, 103 | added ContainerVolumeMounts, 104 | basePath string, 105 | ) (patch []rfc6902PatchOperation) { 106 | for index, container := range target { 107 | volumeMounts, ok := added[container.Name] 108 | if !ok || len(volumeMounts) == 0 { 109 | continue 110 | } 111 | 112 | if len(container.VolumeMounts) == 0 { 113 | volumeMount := volumeMounts[0] 114 | volumeMounts = volumeMounts[1:] 115 | 116 | path := fmt.Sprintf("%s/%d/volumeMounts", basePath, index) 117 | patch = append(patch, rfc6902PatchOperation{ 118 | Op: patchOperationAdd, 119 | Path: path, 120 | Value: []corev1.VolumeMount{volumeMount}, 121 | }) 122 | } 123 | 124 | path := fmt.Sprintf("%s/%d/volumeMounts/-", basePath, index) 125 | for _, volumeMount := range volumeMounts { 126 | patch = append(patch, rfc6902PatchOperation{ 127 | Op: patchOperationAdd, 128 | Path: path, 129 | Value: volumeMount, 130 | }) 131 | } 132 | } 133 | 134 | return patch 135 | } 136 | 137 | // addVolume creates a patch for adding volumes 138 | func addVolume( 139 | target, added []corev1.Volume, 140 | basePath string, 141 | ) (patch []rfc6902PatchOperation) { 142 | first := len(target) == 0 143 | var value interface{} 144 | 145 | for _, add := range added { 146 | value = add 147 | path := basePath 148 | 149 | if first { 150 | first = false 151 | value = []corev1.Volume{add} 152 | } else { 153 | path = path + "/-" 154 | } 155 | 156 | patch = append(patch, rfc6902PatchOperation{ 157 | Op: patchOperationAdd, 158 | Path: path, 159 | Value: value, 160 | }) 161 | } 162 | 163 | return patch 164 | } 165 | 166 | // updateAnnotation creates a patch for adding/updating annotations 167 | func updateAnnotation( 168 | target, added map[string]string, 169 | ) (patch []rfc6902PatchOperation) { 170 | 171 | for key, value := range added { 172 | target[key] = value 173 | } 174 | // Remove annotations that are only for the sidecar injector 175 | for _, value := range sidecarInjectorAnnot { 176 | delete(target, value) 177 | } 178 | path := "/metadata/annotations" 179 | 180 | patch = append(patch, rfc6902PatchOperation{ 181 | Op: patchOperationAdd, 182 | Path: path, 183 | Value: target, 184 | }) 185 | return patch 186 | } 187 | 188 | func printPrettyPatch(patch []byte) string { 189 | var prettyJSON bytes.Buffer 190 | if err := json.Indent(&prettyJSON, patch, "", "\t"); err != nil { 191 | // Could not print pretty, so print ugly 192 | return string(patch) 193 | } 194 | return string(prettyJSON.Bytes()) 195 | } 196 | -------------------------------------------------------------------------------- /pkg/inject/secretless.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | corev1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | type SecretlessSidecarConfig struct { 12 | secretlessConfig string 13 | secretlessCRDSuffix string 14 | conjurConnConfigMapName string 15 | conjurAuthConfigMapName string 16 | serviceAccountTokenVolumeName string 17 | sidecarImage string 18 | } 19 | 20 | // generateSecretlessSidecarConfig generates PatchConfig from a given 21 | // secretlessConfigMapName 22 | func generateSecretlessSidecarConfig(cfg SecretlessSidecarConfig) *PatchConfig { 23 | envvars := []corev1.EnvVar{ 24 | envVarFromFieldPath( 25 | "MY_POD_IP", "status.podIP", 26 | ), 27 | envVarFromFieldPath( 28 | "MY_POD_NAME", "metadata.name", 29 | ), 30 | envVarFromFieldPath( 31 | "MY_POD_NAMESPACE", "metadata.namespace", 32 | ), 33 | } 34 | 35 | if cfg.secretlessCRDSuffix != "" { 36 | envvars = append( 37 | envvars, 38 | envVarFromLiteral( 39 | "SECRETLESS_CRD_SUFFIX", 40 | cfg.secretlessCRDSuffix, 41 | ), 42 | ) 43 | } 44 | 45 | if cfg.conjurConnConfigMapName != "" || cfg.conjurAuthConfigMapName != "" { 46 | envvars = append(envvars, 47 | envVarFromConfigMap( 48 | "CONJUR_ACCOUNT", 49 | cfg.conjurConnConfigMapName, 50 | ), 51 | envVarFromConfigMap( 52 | "CONJUR_APPLIANCE_URL", 53 | cfg.conjurConnConfigMapName, 54 | ), 55 | envVarFromConfigMap( 56 | "CONJUR_AUTHN_LOGIN", 57 | cfg.conjurAuthConfigMapName, 58 | ), 59 | envVarFromConfigMap( 60 | "CONJUR_AUTHN_URL", 61 | cfg.conjurConnConfigMapName, 62 | ), 63 | envVarFromConfigMap( 64 | "CONJUR_SSL_CERTIFICATE", 65 | cfg.conjurConnConfigMapName, 66 | ), 67 | envVarFromConfigMap( 68 | "CONJUR_VERSION", 69 | cfg.conjurConnConfigMapName, 70 | ), 71 | ) 72 | } 73 | 74 | // Sort envvars lexicographically 75 | sort.Slice(envvars, func(i, j int) bool { 76 | return envvars[i].Name < envvars[j].Name 77 | }) 78 | 79 | // Allow configmgr#configspec in the SecretlessConfig annotation 80 | var configMgr string 81 | var configSpec string 82 | var secretlessConfigMapName string 83 | secretlessConfigPath := "/etc/secretless/secretless.yml" 84 | var volumes []corev1.Volume 85 | 86 | // Always add Service Account Token Volume Mount (SATVM) 87 | // It shouldn't be sidecar-injector's responsibility to add the SATVM, we only 88 | // do it here because the serviceaccount plugin which adds the SATVM is 89 | // executed before this plugin injects the sidecar. KEP-36 will solve this 90 | // ordering problem by re-running plugins after mutation. Then if we add a 91 | // container, the serviceaccount plugin will reprocess the manifest and add the 92 | // appropriate mount 93 | // 94 | // ** Remove me when KEP-36 lands ** 95 | // also remove common.getServiceAccountTokenVolumeName 96 | // and calls to that in server.go 97 | volumeMounts := []corev1.VolumeMount{ 98 | { 99 | Name: cfg.serviceAccountTokenVolumeName, 100 | ReadOnly: true, 101 | MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", 102 | }, 103 | } 104 | 105 | // Three options for secretlessConfig 106 | // 1. configmapName 107 | // 2. configfile#configmapname 108 | // 3. k8s/crd#crdName 109 | 110 | // #2 Can't be passed straight through to the broker as its 111 | // expecting configfile#fspath 112 | 113 | if strings.Contains(cfg.secretlessConfig, "#") { 114 | // configmgr#configspec 115 | parts := strings.Split(cfg.secretlessConfig, "#") 116 | configMgr = parts[0] 117 | configSpec = parts[1] 118 | 119 | // option 2 120 | if configMgr == "configfile" { 121 | secretlessConfigMapName = configSpec 122 | configSpec = secretlessConfigPath 123 | } 124 | } else { 125 | // option 1 126 | // Old format, contains config map name only. 127 | configMgr = "configfile" 128 | secretlessConfigMapName = cfg.secretlessConfig 129 | configSpec = secretlessConfigPath 130 | } 131 | 132 | // if configMgr is k8s/crd, no further config is required. 133 | if configMgr == "configfile" { 134 | 135 | // Add configmap volume 136 | volumes = append(volumes, corev1.Volume{ 137 | Name: "secretless-config", 138 | VolumeSource: corev1.VolumeSource{ 139 | ConfigMap: &corev1.ConfigMapVolumeSource{ 140 | LocalObjectReference: corev1.LocalObjectReference{ 141 | Name: secretlessConfigMapName, 142 | }, 143 | }, 144 | }, 145 | }, 146 | ) 147 | 148 | // Add configmap mount 149 | volumeMounts = append(volumeMounts, corev1.VolumeMount{ 150 | Name: "secretless-config", 151 | ReadOnly: true, 152 | MountPath: "/etc/secretless", 153 | }, 154 | ) 155 | } 156 | 157 | containers := []corev1.Container{ 158 | { 159 | Name: "secretless", 160 | Image: cfg.sidecarImage, 161 | Args: []string{ 162 | "-config-mgr", 163 | fmt.Sprintf("%s#%s", configMgr, configSpec), 164 | }, 165 | ImagePullPolicy: "Always", 166 | VolumeMounts: volumeMounts, 167 | Env: envvars, 168 | }, 169 | } 170 | 171 | return &PatchConfig{ 172 | Containers: containers, 173 | Volumes: volumes, 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /pkg/inject/secretless_test.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSecretlessSidecarInjection(t *testing.T) { 11 | var testCases = []injectionTestCase{ 12 | { 13 | description: "Secretless", 14 | annotatedPodTemplateSpecPath: "./testdata/secretless-annotated-pod.json", 15 | expectedInjectedPodTemplateSpecPath: "./testdata/secretless-mutated-pod.json", 16 | }, 17 | { 18 | description: "Secretless with image name", 19 | annotatedPodTemplateSpecPath: "./testdata/secretless-annotated-pod-with-image.json", 20 | expectedInjectedPodTemplateSpecPath: "./testdata/secretless-mutated-pod-with-image.json", 21 | }, 22 | } 23 | 24 | for _, tc := range testCases { 25 | t.Run(tc.description, func(t *testing.T) { 26 | // Create the Admission Request (wrapped in an Admission Review) from the 27 | // annotated pod fixture. The goal is to use the annotations as a signal to 28 | // the sidecar-injector to mutate the Pod template spec. 29 | req, err := newTestAdmissionRequest( 30 | tc.annotatedPodTemplateSpecPath, 31 | ) 32 | if !assert.NoError(t, err) { 33 | return 34 | } 35 | 36 | // Read the expected Pod template spec fixture. 37 | expectedMod, err := ioutil.ReadFile( 38 | tc.expectedInjectedPodTemplateSpecPath, 39 | ) 40 | if !assert.NoError(t, err) { 41 | return 42 | } 43 | 44 | // Get the modified Pod template spec from the input. 45 | mod, err := applyPatchToAdmissionRequest(req) 46 | if !assert.NoError(t, err) { 47 | return 48 | } 49 | 50 | // Assert that the modified Pod template spec should equal the expected Pod 51 | // template spec. 52 | assert.JSONEq(t, string(expectedMod), string(mod)) 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pkg/inject/secrets-provider.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | "os" 6 | ) 7 | 8 | type SecretsProviderSidecarConfig struct { 9 | containerMode string 10 | containerName string 11 | sidecarImage string 12 | secretsDestination string 13 | } 14 | 15 | var conjurEnvVars = []string{ 16 | "CONJUR_ACCOUNT", 17 | "conjurAccount", 18 | "CONJUR_APPLIANCE_URL", 19 | "conjurApplianceUrl", 20 | "CONJUR_AUTHENTICATOR_ID", 21 | "authnK8sAuthenticatorID", 22 | "CONJUR_AUTHN_URL", 23 | "CONJUR_SSL_CERTIFICATE", 24 | "conjurSslCertificate", 25 | } 26 | 27 | func getConjurAuthnURL(envVars map[string]string) string { 28 | 29 | authnMethod := "authn-k8s" 30 | // If "CONJUR_AUTHN_URL" is explicitly set, use it 31 | if envVars["CONJUR_AUTHN_URL"] != "" { 32 | return envVars["CONJUR_AUTHN_URL"] 33 | } 34 | return envVars["conjurApplianceUrl"] + "/" + authnMethod + "/" + 35 | envVars["authnK8sAuthenticatorID"] 36 | } 37 | func getConjurEnv(envVars map[string]string, primary string, 38 | secondary string) string { 39 | 40 | if envVars[primary] != "" { 41 | return envVars[primary] 42 | } 43 | return envVars[secondary] 44 | } 45 | 46 | // generateSecretsProviderSidecarConfig generates PatchConfig from a 47 | // given SecretsProviderSidecarConfig 48 | func generateSecretsProviderSidecarConfig( 49 | cfg SecretsProviderSidecarConfig, 50 | ) *PatchConfig { 51 | var containers, initContainers []corev1.Container 52 | envVars := make(map[string]string) 53 | for _, envVar := range conjurEnvVars { 54 | value := os.Getenv(envVar) 55 | if value != "" { 56 | envVars[envVar] = value 57 | } 58 | } 59 | 60 | volumeMounts := []corev1.VolumeMount{ 61 | { 62 | Name: "podinfo", 63 | ReadOnly: true, 64 | MountPath: "/conjur/podinfo", 65 | }, 66 | { 67 | Name: "conjur-status", 68 | ReadOnly: false, 69 | MountPath: "/conjur/status", 70 | }, 71 | } 72 | if cfg.secretsDestination == "file" { 73 | volumeMount := corev1.VolumeMount{ 74 | Name: "conjur-secrets", 75 | ReadOnly: false, 76 | MountPath: "/conjur/secrets", 77 | } 78 | volumeMounts = append(volumeMounts, volumeMount) 79 | } 80 | container := corev1.Container{ 81 | Name: cfg.containerName, 82 | Image: cfg.sidecarImage, 83 | ImagePullPolicy: "Always", 84 | VolumeMounts: volumeMounts, 85 | Env: []corev1.EnvVar{ 86 | envVarFromFieldPath( 87 | "MY_POD_NAME", 88 | "metadata.name", 89 | ), 90 | envVarFromFieldPath( 91 | "MY_POD_NAMESPACE", 92 | "metadata.namespace", 93 | ), 94 | envVarFromLiteral( 95 | "CONJUR_ACCOUNT", 96 | getConjurEnv(envVars, "CONJUR_ACCOUNT", "conjurAccount"), 97 | ), 98 | envVarFromLiteral( 99 | "CONJUR_APPLIANCE_URL", 100 | getConjurEnv(envVars, "CONJUR_APPLIANCE_URL", "conjurApplianceUrl"), 101 | ), 102 | envVarFromLiteral( 103 | "CONJUR_AUTHENTICATOR_ID", 104 | getConjurEnv(envVars, "CONJUR_AUTHENTICATOR_ID", "authnK8sAuthenticatorID"), 105 | ), 106 | envVarFromLiteral( 107 | "CONJUR_AUTHN_URL", 108 | getConjurAuthnURL(envVars), 109 | ), 110 | envVarFromLiteral( 111 | "CONJUR_SSL_CERTIFICATE", 112 | getConjurEnv(envVars, "CONJUR_SSL_CERTIFICATE", "conjurSslCertificate"), 113 | ), 114 | }, 115 | } 116 | 117 | candidates := []corev1.Container{container} 118 | if cfg.containerMode == "init" { 119 | initContainers = candidates 120 | } else { 121 | containers = candidates 122 | } 123 | volumes := getSPVolumes(cfg.secretsDestination) 124 | 125 | return &PatchConfig{ 126 | Containers: containers, 127 | InitContainers: initContainers, 128 | Volumes: volumes, 129 | } 130 | } 131 | 132 | func getSPVolumes(secretsDest string) []corev1.Volume { 133 | 134 | var volume corev1.Volume 135 | 136 | volumes := []corev1.Volume{ 137 | { 138 | Name: "podinfo", 139 | VolumeSource: corev1.VolumeSource{ 140 | DownwardAPI: &corev1.DownwardAPIVolumeSource{ 141 | Items: []corev1.DownwardAPIVolumeFile{ 142 | { 143 | Path: "annotations", 144 | FieldRef: &corev1.ObjectFieldSelector{ 145 | FieldPath: "metadata.annotations", 146 | }, 147 | }, 148 | }, 149 | }, 150 | }, 151 | }, 152 | { 153 | Name: "conjur-status", 154 | VolumeSource: corev1.VolumeSource{ 155 | EmptyDir: &corev1.EmptyDirVolumeSource{ 156 | Medium: "Memory", 157 | }, 158 | }, 159 | }, 160 | } 161 | if secretsDest == "file" { 162 | volume = corev1.Volume{ 163 | Name: "conjur-secrets", 164 | VolumeSource: corev1.VolumeSource{ 165 | EmptyDir: &corev1.EmptyDirVolumeSource{ 166 | Medium: "Memory", 167 | }, 168 | }, 169 | } 170 | volumes = append(volumes, volume) 171 | } 172 | return volumes 173 | } 174 | -------------------------------------------------------------------------------- /pkg/inject/secrets-provider_test.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestSecretsProviderSidecarInjection(t *testing.T) { 11 | var testCases = []injectionTestCase{ 12 | { 13 | description: "SecretsProvider sidecar", 14 | annotatedPodTemplateSpecPath: "./testdata/secrets-provider-annotated-pod.json", 15 | expectedInjectedPodTemplateSpecPath: "./testdata/secrets-provider-mutated-pod.json", 16 | env: map[string]string{ 17 | "CONJUR_ACCOUNT": "myConjurAccount", 18 | "CONJUR_APPLIANCE_URL": "https://conjur-oss.conjur-oss.svc.cluster.local", 19 | "CONJUR_AUTHENTICATOR_ID": "my-authenticator-id", 20 | "CONJUR_AUTHN_URL": "https://conjur-oss.conjur-oss.svc.cluster.local/authn-k8s/my-authenticator-id", 21 | "CONJUR_SSL_CERTIFICATE": "-----BEGIN CERTIFICATE-----tVw0ZnjsOV2ZeIBRalX/72RplPzkmWKAw==\n-----END CERTIFICATE-----\n", 22 | }, 23 | }, 24 | { 25 | description: "SecretsProvider init", 26 | annotatedPodTemplateSpecPath: "./testdata/secrets-provider-init-annotated-pod.json", 27 | expectedInjectedPodTemplateSpecPath: "./testdata/secrets-provider-init-mutated-pod.json", 28 | env: map[string]string{ 29 | "CONJUR_ACCOUNT": "myConjurAccount", 30 | "CONJUR_APPLIANCE_URL": "https://conjur-oss.conjur-oss.svc.cluster.local", 31 | "CONJUR_AUTHENTICATOR_ID": "my-authenticator-id", 32 | "CONJUR_AUTHN_URL": "https://conjur-oss.conjur-oss.svc.cluster.local/authn-k8s/my-authenticator-id", 33 | "CONJUR_SSL_CERTIFICATE": "-----BEGIN CERTIFICATE-----tVw0ZnjsOV2ZeIBRalX/72RplPzkmWKAw==\n-----END CERTIFICATE-----\n", 34 | }, 35 | }, 36 | { 37 | description: "SecretsProvider golden config", 38 | annotatedPodTemplateSpecPath: "./testdata/secrets-provider-annotated-pod.json", 39 | expectedInjectedPodTemplateSpecPath: "./testdata/secrets-provider-mutated-pod.json", 40 | env: map[string]string{ 41 | "conjurAccount": "myConjurAccount", 42 | "conjurApplianceUrl": "https://conjur-oss.conjur-oss.svc.cluster.local", 43 | "authnK8sAuthenticatorID": "my-authenticator-id", 44 | "conjurSslCertificate": "-----BEGIN CERTIFICATE-----tVw0ZnjsOV2ZeIBRalX/72RplPzkmWKAw==\n-----END CERTIFICATE-----\n", 45 | }, 46 | }, 47 | } 48 | for _, tc := range testCases { 49 | t.Run(tc.description, func(t *testing.T) { 50 | for envVar, value := range tc.env { 51 | os.Setenv(envVar, value) 52 | } 53 | // Create the Admission Request (wrapped in an Admission Review) from the 54 | // annotated pod fixture. The goal is to use the annotations as a signal to 55 | // the sidecar-injector to mutate the Pod template spec. 56 | req, err := newTestAdmissionRequest( 57 | tc.annotatedPodTemplateSpecPath, 58 | ) 59 | if !assert.NoError(t, err) { 60 | return 61 | } 62 | 63 | // Read the expected Pod template spec fixture. 64 | expectedMod, err := ioutil.ReadFile( 65 | tc.expectedInjectedPodTemplateSpecPath, 66 | ) 67 | if !assert.NoError(t, err) { 68 | return 69 | } 70 | 71 | // Get the modified Pod template spec from the input. 72 | mod, err := applyPatchToAdmissionRequest(req) 73 | if !assert.NoError(t, err) { 74 | return 75 | } 76 | 77 | // Assert that the modified Pod template spec should equal the expected Pod 78 | // template spec. 79 | assert.JSONEq(t, string(expectedMod), string(mod)) 80 | for envVar, _ := range tc.env { 81 | os.Unsetenv(envVar) 82 | } 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /pkg/inject/server.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "strings" 10 | 11 | admissionv1 "k8s.io/api/admission/v1" 12 | admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 13 | v1 "k8s.io/api/apps/v1" 14 | corev1 "k8s.io/api/core/v1" 15 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "k8s.io/apimachinery/pkg/runtime" 17 | "k8s.io/apimachinery/pkg/runtime/serializer" 18 | ) 19 | 20 | var ( 21 | runtimeScheme = runtime.NewScheme() 22 | codecs = serializer.NewCodecFactory(runtimeScheme) 23 | deserializer = codecs.UniversalDeserializer() 24 | 25 | // (https://github.com/kubernetes/kubernetes/issues/57982) 26 | defaulter = runtime.ObjectDefaulter(runtimeScheme) 27 | ) 28 | 29 | func init() { 30 | _ = corev1.AddToScheme(runtimeScheme) 31 | _ = admissionregistrationv1.AddToScheme(runtimeScheme) 32 | // defaulting with webhooks: 33 | // https://github.com/kubernetes/kubernetes/issues/57982 34 | _ = v1.AddToScheme(runtimeScheme) 35 | } 36 | 37 | var ignoredNamespaces = []string{ 38 | metav1.NamespaceSystem, 39 | metav1.NamespacePublic, 40 | } 41 | 42 | type WebhookServer struct { 43 | Server *http.Server 44 | Params WebhookServerParameters 45 | } 46 | 47 | // Webhook Server parameters 48 | type WebhookServerParameters struct { 49 | NoHTTPS bool // Runs an HTTP server when true 50 | Port int // Webhook Server port 51 | CertFile string // Path to the x509 certificate for https 52 | KeyFile string // Path to the x509 private key matching `CertFile` 53 | SecretlessContainerImage string // Container image for the Secretless sidecar 54 | AuthenticatorContainerImage string // Container image for the K8s Authenticator sidecar 55 | SecretsProviderContainerImage string // Container image for the Secrets Provider sidecar 56 | } 57 | 58 | func failWithResponse(errMsg string) admissionv1.AdmissionResponse { 59 | log.Printf(errMsg) 60 | return admissionv1.AdmissionResponse{ 61 | Result: &metav1.Status{ 62 | Message: errMsg, 63 | }, 64 | } 65 | } 66 | 67 | // SidecarInjectorConfig are configuration values for the sidecar injector logic 68 | type SidecarInjectorConfig struct { 69 | SecretlessContainerImage string // Container image for the Secretless sidecar 70 | AuthenticatorContainerImage string // Container image for the K8s Authenticator sidecar 71 | SecretsProviderContainerImage string // Container image for the Secrets Provider 72 | } 73 | 74 | // HandleAdmissionRequest applies the sidecar-injector logic to the AdmissionRequest 75 | // and returns the results as an AdmissionResponse. 76 | func HandleAdmissionRequest( 77 | sidecarInjectorConfig SidecarInjectorConfig, 78 | req *admissionv1.AdmissionRequest, 79 | ) admissionv1.AdmissionResponse { 80 | if req == nil { 81 | return failWithResponse("Received empty request") 82 | } 83 | 84 | var pod corev1.Pod 85 | if err := json.Unmarshal(req.Object.Raw, &pod); err != nil { 86 | return failWithResponse( 87 | fmt.Sprintf("Could not unmarshal raw object: %v", err), 88 | ) 89 | } 90 | 91 | log.Printf( 92 | "AdmissionRequest for Version=%s, Kind=%s, Namespace=%v PodName=%v UID=%v rfc6902PatchOperation=%v UserInfo=%v", 93 | req.Kind.Version, 94 | req.Kind.Kind, 95 | req.Namespace, 96 | metaName(&pod.ObjectMeta), 97 | req.UID, 98 | req.Operation, 99 | req.UserInfo, 100 | ) 101 | 102 | // Determine whether to perform mutation 103 | if !mutationRequired(ignoredNamespaces, &pod.ObjectMeta) { 104 | log.Printf( 105 | "Skipping mutation for %s/%s due to policy check", 106 | req.Namespace, 107 | metaName(&pod.ObjectMeta), 108 | ) 109 | 110 | return admissionv1.AdmissionResponse{ 111 | Allowed: true, 112 | } 113 | } 114 | 115 | injectType, err := getAnnotation(&pod.ObjectMeta, annotationInjectTypeKey) 116 | containerMode, err := getAnnotation(&pod.ObjectMeta, annotationContainerModeKey) 117 | containerName, err := getAnnotation(&pod.ObjectMeta, annotationContainerNameKey) 118 | conjurInjectVolumeStr, err := getAnnotation( 119 | &pod.ObjectMeta, 120 | annotationConjurInjectVolumesKey, 121 | ) 122 | conjurInjectVolume := strings.Split(conjurInjectVolumeStr, ",") 123 | for i := range conjurInjectVolume { 124 | conjurInjectVolume[i] = strings.TrimSpace(conjurInjectVolume[i]) 125 | } 126 | var sidecarConfig *PatchConfig 127 | annotations := make(map[string]string) 128 | annotations[annotationStatusKey] = "injected" 129 | 130 | switch injectType { 131 | case "secretless": 132 | 133 | secretlessConfig, err := getAnnotation( 134 | &pod.ObjectMeta, 135 | annotationSecretlessConfigKey, 136 | ) 137 | if err != nil { 138 | return failWithResponse( 139 | fmt.Sprintf( 140 | "Mutation failed for pod %s, in namespace %s, due to %s", 141 | pod.Name, 142 | req.Namespace, 143 | err.Error(), 144 | ), 145 | ) 146 | } 147 | 148 | secretlessCRDSuffix, _ := getAnnotation(&pod.ObjectMeta, 149 | annotationSecretlessCRDSuffixKey) 150 | 151 | conjurConnConfigMapName, _ := getAnnotation( 152 | &pod.ObjectMeta, 153 | annotationConjurConnConfigKey, 154 | ) 155 | conjurAuthConfigMapName, _ := getAnnotation( 156 | &pod.ObjectMeta, 157 | annotationConjurAuthConfigKey, 158 | ) 159 | 160 | imageName, err := getAnnotation( 161 | &pod.ObjectMeta, 162 | annotationContainerImageKey, 163 | ) 164 | if err != nil { 165 | imageName = sidecarInjectorConfig.SecretlessContainerImage 166 | } 167 | 168 | ServiceAccountTokenVolumeName, err := getServiceAccountTokenVolumeName(&pod) 169 | if err != nil { 170 | return failWithResponse( 171 | fmt.Sprintf( 172 | "Mutation failed for pod %s, in namespace %s, due to %s", 173 | pod.Name, 174 | req.Namespace, 175 | err.Error(), 176 | ), 177 | ) 178 | } 179 | 180 | sidecarConfig = generateSecretlessSidecarConfig( 181 | SecretlessSidecarConfig{ 182 | secretlessConfig: secretlessConfig, 183 | secretlessCRDSuffix: secretlessCRDSuffix, 184 | conjurConnConfigMapName: conjurConnConfigMapName, 185 | conjurAuthConfigMapName: conjurAuthConfigMapName, 186 | serviceAccountTokenVolumeName: ServiceAccountTokenVolumeName, 187 | sidecarImage: imageName, 188 | }, 189 | ) 190 | break 191 | case "authenticator": 192 | conjurAuthConfigMapName, err := getAnnotation( 193 | &pod.ObjectMeta, 194 | annotationConjurAuthConfigKey, 195 | ) 196 | if err != nil { 197 | return failWithResponse( 198 | fmt.Sprintf( 199 | "Mutation failed for pod %s, in namespace %s, due to %s", 200 | pod.Name, 201 | req.Namespace, 202 | err.Error(), 203 | ), 204 | ) 205 | } 206 | 207 | conjurConnConfigMapName, err := getAnnotation( 208 | &pod.ObjectMeta, 209 | annotationConjurConnConfigKey, 210 | ) 211 | if err != nil { 212 | return failWithResponse( 213 | fmt.Sprintf( 214 | "Mutation failed for pod %s, in namespace %s, due to %s", 215 | pod.Name, 216 | req.Namespace, 217 | err.Error(), 218 | ), 219 | ) 220 | } 221 | 222 | switch containerMode { 223 | case "sidecar", "init", "": 224 | break 225 | default: 226 | return failWithResponse( 227 | fmt.Sprintf( 228 | "Mutation failed for pod %s, in namespace %s, due to %s value (%s) not supported", 229 | pod.Name, 230 | req.Namespace, 231 | annotationContainerModeKey, 232 | containerMode, 233 | ), 234 | ) 235 | } 236 | 237 | imageName, err := getAnnotation( 238 | &pod.ObjectMeta, 239 | annotationContainerImageKey, 240 | ) 241 | if err != nil { 242 | imageName = sidecarInjectorConfig.AuthenticatorContainerImage 243 | } 244 | 245 | sidecarConfig = generateAuthenticatorSidecarConfig(AuthenticatorSidecarConfig{ 246 | conjurConnConfigMapName: conjurConnConfigMapName, 247 | conjurAuthConfigMapName: conjurAuthConfigMapName, 248 | containerMode: containerMode, 249 | containerName: containerName, 250 | sidecarImage: imageName, 251 | }) 252 | 253 | containerVolumeMounts := ContainerVolumeMounts{} 254 | for _, receiveContainerName := range conjurInjectVolume { 255 | containerVolumeMounts[receiveContainerName] = []corev1.VolumeMount{ 256 | { 257 | Name: "conjur-access-token", 258 | ReadOnly: true, 259 | MountPath: "/run/conjur", 260 | }, 261 | } 262 | } 263 | sidecarConfig.ContainerVolumeMounts = containerVolumeMounts 264 | 265 | break 266 | case "secrets-provider": 267 | containerImage, err := getAnnotation( 268 | &pod.ObjectMeta, 269 | annotationContainerImageKey, 270 | ) 271 | if err != nil { 272 | containerImage = sidecarInjectorConfig.SecretsProviderContainerImage 273 | log.Printf("Using container image %s", containerImage) 274 | } 275 | switch containerMode { 276 | case "sidecar", "init", "": 277 | break 278 | default: 279 | return failWithResponse( 280 | fmt.Sprintf( 281 | "Mutation failed for pod %s, in namespace %s, due to %s value (%s) not supported", 282 | pod.Name, 283 | req.Namespace, 284 | annotationContainerModeKey, 285 | containerMode, 286 | ), 287 | ) 288 | } 289 | secretsDestination, err := getAnnotation( 290 | &pod.ObjectMeta, 291 | annotationSecretsDestinationKey, 292 | ) 293 | sidecarConfig = generateSecretsProviderSidecarConfig( 294 | SecretsProviderSidecarConfig{ 295 | containerMode: containerMode, 296 | containerName: containerName, 297 | sidecarImage: containerImage, 298 | secretsDestination: secretsDestination, 299 | }, 300 | ) 301 | containerVolumeMounts := ContainerVolumeMounts{} 302 | 303 | for _, receiveContainerName := range conjurInjectVolume { 304 | containerVolumeMounts[receiveContainerName] = []corev1.VolumeMount{ 305 | { 306 | Name: "conjur-status", 307 | ReadOnly: false, 308 | MountPath: "/conjur/status", 309 | }, 310 | { 311 | Name: "conjur-secrets", 312 | ReadOnly: false, 313 | MountPath: "/conjur/secrets", 314 | }, 315 | } 316 | } 317 | sidecarConfig.ContainerVolumeMounts = containerVolumeMounts 318 | break 319 | default: 320 | errMsg := fmt.Sprintf( 321 | "Mutation failed for pod %s, in namespace %s, due to invalid inject type annotation value = %s", 322 | pod.Name, 323 | req.Namespace, 324 | injectType, 325 | ) 326 | log.Printf(errMsg) 327 | 328 | return admissionv1.AdmissionResponse{ 329 | Result: &metav1.Status{ 330 | Message: errMsg, 331 | }, 332 | } 333 | } 334 | 335 | patchBytes, err := createPatch(&pod, sidecarConfig, annotations) 336 | if err != nil { 337 | return admissionv1.AdmissionResponse{ 338 | Result: &metav1.Status{ 339 | Message: err.Error(), 340 | }, 341 | } 342 | } 343 | 344 | log.Printf("AdmissionResponse: patch=%v\n", printPrettyPatch(patchBytes)) 345 | return admissionv1.AdmissionResponse{ 346 | Allowed: true, 347 | Patch: patchBytes, 348 | PatchType: func() *admissionv1.PatchType { 349 | pt := admissionv1.PatchTypeJSONPatch 350 | return &pt 351 | }(), 352 | } 353 | } 354 | 355 | // Serve method for webhook Server 356 | func (whsvr *WebhookServer) Serve(w http.ResponseWriter, r *http.Request) { 357 | var body []byte 358 | if r.Body != nil { 359 | if data, err := ioutil.ReadAll(r.Body); err == nil { 360 | body = data 361 | } 362 | } 363 | 364 | if len(body) == 0 { 365 | log.Print("empty body") 366 | http.Error(w, "empty body", http.StatusBadRequest) 367 | return 368 | } 369 | 370 | // verify the content type is accurate 371 | contentType := r.Header.Get("Content-Type") 372 | if contentType != "application/json" { 373 | log.Printf("Content-Type=%s, expecting application/json", contentType) 374 | http.Error(w, "invalid Content-Type, expecting `application/json`", http.StatusUnsupportedMediaType) 375 | return 376 | } 377 | 378 | // Declare AdmissionResponse. This is the value that will be used to craft the 379 | // response on this handler. 380 | var admissionResponse admissionv1.AdmissionResponse 381 | 382 | // Decode AdmissionRequest from raw AdmissionReview bytes 383 | admissionRequest, err := NewAdmissionRequest(body) 384 | if err != nil { 385 | log.Printf("could not decode body: %v", err) 386 | 387 | // Set AdmissionResponse with error message 388 | admissionResponse = admissionv1.AdmissionResponse{ 389 | UID: admissionRequest.UID, 390 | Result: &metav1.Status{ 391 | Message: err.Error(), 392 | }, 393 | } 394 | } else { 395 | // Set AdmissionResponse with results from HandleAdmissionRequest 396 | admissionResponse = HandleAdmissionRequest( 397 | SidecarInjectorConfig{ 398 | SecretlessContainerImage: whsvr.Params.SecretlessContainerImage, 399 | AuthenticatorContainerImage: whsvr.Params.AuthenticatorContainerImage, 400 | SecretsProviderContainerImage: whsvr.Params.SecretsProviderContainerImage, 401 | }, 402 | admissionRequest, 403 | ) 404 | } 405 | 406 | // Ensure the response has the same UID as the original request (if the request field 407 | // was populated) 408 | admissionResponse.UID = admissionRequest.UID 409 | 410 | // Wrap AdmissonResponse in AdmissionReview, then marshal it to JSON 411 | resp, err := json.Marshal(admissionv1.AdmissionReview{ 412 | TypeMeta: metav1.TypeMeta{ 413 | APIVersion: "admission.k8s.io/v1", 414 | Kind: "AdmissionReview", 415 | }, 416 | Response: &admissionResponse, 417 | }) 418 | if err != nil { 419 | log.Printf("could not encode response: %v", err) 420 | http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError) 421 | } 422 | log.Printf("Ready to write response ...") 423 | if _, err := w.Write(resp); err != nil { 424 | log.Printf("could not write response: %v", err) 425 | http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError) 426 | } 427 | } 428 | 429 | // NewAdmissionRequest parses raw bytes to create an AdmissionRequest. AdmissionRequest 430 | // actually comes wrapped inside the bytes of an AdmissionReview. 431 | func NewAdmissionRequest(reviewRequestBytes []byte) (*admissionv1.AdmissionRequest, error) { 432 | var ar admissionv1.AdmissionReview 433 | _, _, err := deserializer.Decode(reviewRequestBytes, nil, &ar) 434 | 435 | log.Printf("Received AdmissionReview, APIVersion: %s, Kind: %s\n", ar.APIVersion, ar.Kind) 436 | return ar.Request, err 437 | } 438 | -------------------------------------------------------------------------------- /pkg/inject/testdata/authenticator-admission-request.tmpl.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "AdmissionReview", 3 | "apiVersion": "admission.k8s.io/v1", 4 | "request": { 5 | "uid": "0df28fbd-5f5f-11e8-bc74-36e6bb280816", 6 | "kind": { 7 | "group": "", 8 | "version": "v1", 9 | "kind": "Pod" 10 | }, 11 | "resource": { 12 | "group": "", 13 | "version": "v1", 14 | "resource": "pods" 15 | }, 16 | "namespace": "dummy", 17 | "operation": "CREATE", 18 | "userInfo": { 19 | "username": "system:serviceaccount:kube-system:replicaset-controller", 20 | "uid": "a7e0ab33-5f29-11e8-8a3c-36e6bb280816", 21 | "groups": [ 22 | "system:serviceaccounts", 23 | "system:serviceaccounts:kube-system", 24 | "system:authenticated" 25 | ] 26 | }, 27 | "oldObject": null, 28 | "object": {{.}} 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/inject/testdata/authenticator-annotated-pod-with-image.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/conjurAuthConfig": "conjur", 10 | "conjur.org/conjurConnConfig": "conjur", 11 | "conjur.org/container-mode": "sidecar", 12 | "conjur.org/conjur-inject-volumes": "nginx-2", 13 | "conjur.org/inject": "true", 14 | "conjur.org/inject-type": "authenticator", 15 | "conjur.org/container-name": "authenticator-name", 16 | "conjur.org/container-image": "cyberark/conjur-authn-k8s-client:12345" 17 | } 18 | }, 19 | "spec": { 20 | "volumes": [ 21 | { 22 | "name": "default-token-tq5lq", 23 | "secret": { 24 | "secretName": "default-token-tq5lq" 25 | } 26 | } 27 | ], 28 | "containers": [ 29 | { 30 | "name": "nginx-1", 31 | "image": "nginx:1.7.9", 32 | "volumeMounts": [ 33 | { 34 | "name": "default-token-tq5lq", 35 | "readOnly": true, 36 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 37 | } 38 | ] 39 | }, 40 | { 41 | "name": "nginx-2", 42 | "image": "nginx:1.7.9", 43 | "volumeMounts": [ 44 | { 45 | "name": "default-token-tq5lq", 46 | "readOnly": true, 47 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/inject/testdata/authenticator-annotated-pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/conjurAuthConfig": "conjur", 10 | "conjur.org/conjurConnConfig": "conjur", 11 | "conjur.org/container-mode": "sidecar", 12 | "conjur.org/conjur-inject-volumes": "nginx-2", 13 | "conjur.org/inject": "true", 14 | "conjur.org/inject-type": "authenticator", 15 | "conjur.org/container-name": "authenticator-name" 16 | } 17 | }, 18 | "spec": { 19 | "volumes": [ 20 | { 21 | "name": "default-token-tq5lq", 22 | "secret": { 23 | "secretName": "default-token-tq5lq" 24 | } 25 | } 26 | ], 27 | "containers": [ 28 | { 29 | "name": "nginx-1", 30 | "image": "nginx:1.7.9", 31 | "volumeMounts": [ 32 | { 33 | "name": "default-token-tq5lq", 34 | "readOnly": true, 35 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 36 | } 37 | ] 38 | }, 39 | { 40 | "name": "nginx-2", 41 | "image": "nginx:1.7.9", 42 | "volumeMounts": [ 43 | { 44 | "name": "default-token-tq5lq", 45 | "readOnly": true, 46 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/inject/testdata/authenticator-mutated-pod-with-image.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/container-mode": "sidecar", 10 | "conjur.org/status": "injected" 11 | } 12 | }, 13 | "spec": { 14 | "volumes": [ 15 | { 16 | "name": "default-token-tq5lq", 17 | "secret": { 18 | "secretName": "default-token-tq5lq" 19 | } 20 | }, 21 | { 22 | "name": "conjur-access-token", 23 | "emptyDir": { 24 | "medium": "Memory" 25 | } 26 | } 27 | ], 28 | "containers": [ 29 | { 30 | "name": "nginx-1", 31 | "image": "nginx:1.7.9", 32 | "volumeMounts": [ 33 | { 34 | "name": "default-token-tq5lq", 35 | "readOnly": true, 36 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 37 | } 38 | ] 39 | }, 40 | { 41 | "name": "nginx-2", 42 | "image": "nginx:1.7.9", 43 | "volumeMounts": [ 44 | { 45 | "name": "default-token-tq5lq", 46 | "readOnly": true, 47 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 48 | }, 49 | { 50 | "name": "conjur-access-token", 51 | "readOnly": true, 52 | "mountPath": "/run/conjur" 53 | } 54 | ] 55 | }, 56 | { 57 | "name": "authenticator-name", 58 | "image": "cyberark/conjur-authn-k8s-client:12345", 59 | "env": [ 60 | { 61 | "name": "CONJUR_ACCOUNT", 62 | "valueFrom": { 63 | "configMapKeyRef": { 64 | "name": "conjur", 65 | "key": "CONJUR_ACCOUNT" 66 | } 67 | } 68 | }, 69 | { 70 | "name": "CONJUR_APPLIANCE_URL", 71 | "valueFrom": { 72 | "configMapKeyRef": { 73 | "name": "conjur", 74 | "key": "CONJUR_APPLIANCE_URL" 75 | } 76 | } 77 | }, 78 | { 79 | "name": "CONJUR_AUTHN_LOGIN", 80 | "valueFrom": { 81 | "configMapKeyRef": { 82 | "name": "conjur", 83 | "key": "CONJUR_AUTHN_LOGIN" 84 | } 85 | } 86 | }, 87 | { 88 | "name": "CONJUR_AUTHN_TOKEN_FILE", 89 | "value": "/run/conjur/conjur-access-token" 90 | }, 91 | { 92 | "name": "CONJUR_AUTHN_URL", 93 | "valueFrom": { 94 | "configMapKeyRef": { 95 | "name": "conjur", 96 | "key": "CONJUR_AUTHN_URL" 97 | } 98 | } 99 | }, 100 | { 101 | "name": "CONJUR_SSL_CERTIFICATE", 102 | "valueFrom": { 103 | "configMapKeyRef": { 104 | "name": "conjur", 105 | "key": "CONJUR_SSL_CERTIFICATE" 106 | } 107 | } 108 | }, 109 | { 110 | "name": "CONJUR_VERSION", 111 | "valueFrom": { 112 | "configMapKeyRef": { 113 | "name": "conjur", 114 | "key": "CONJUR_VERSION" 115 | } 116 | } 117 | }, 118 | { 119 | "name": "CONTAINER_MODE", 120 | "value": "sidecar" 121 | }, 122 | { 123 | "name": "MY_POD_IP", 124 | "valueFrom": { 125 | "fieldRef": { 126 | "fieldPath": "status.podIP" 127 | } 128 | } 129 | }, 130 | { 131 | "name": "MY_POD_NAME", 132 | "valueFrom": { 133 | "fieldRef": { 134 | "fieldPath": "metadata.name" 135 | } 136 | } 137 | }, 138 | { 139 | "name": "MY_POD_NAMESPACE", 140 | "valueFrom": { 141 | "fieldRef": { 142 | "fieldPath": "metadata.namespace" 143 | } 144 | } 145 | } 146 | ], 147 | "resources": {}, 148 | "volumeMounts": [ 149 | { 150 | "name": "conjur-access-token", 151 | "mountPath": "/run/conjur" 152 | } 153 | ], 154 | "imagePullPolicy": "Always" 155 | } 156 | ] 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /pkg/inject/testdata/authenticator-mutated-pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/container-mode": "sidecar", 10 | "conjur.org/status": "injected" 11 | } 12 | }, 13 | "spec": { 14 | "volumes": [ 15 | { 16 | "name": "default-token-tq5lq", 17 | "secret": { 18 | "secretName": "default-token-tq5lq" 19 | } 20 | }, 21 | { 22 | "name": "conjur-access-token", 23 | "emptyDir": { 24 | "medium": "Memory" 25 | } 26 | } 27 | ], 28 | "containers": [ 29 | { 30 | "name": "nginx-1", 31 | "image": "nginx:1.7.9", 32 | "volumeMounts": [ 33 | { 34 | "name": "default-token-tq5lq", 35 | "readOnly": true, 36 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 37 | } 38 | ] 39 | }, 40 | { 41 | "name": "nginx-2", 42 | "image": "nginx:1.7.9", 43 | "volumeMounts": [ 44 | { 45 | "name": "default-token-tq5lq", 46 | "readOnly": true, 47 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 48 | }, 49 | { 50 | "name": "conjur-access-token", 51 | "readOnly": true, 52 | "mountPath": "/run/conjur" 53 | } 54 | ] 55 | }, 56 | { 57 | "name": "authenticator-name", 58 | "image": "authenticator-image", 59 | "env": [ 60 | { 61 | "name": "CONJUR_ACCOUNT", 62 | "valueFrom": { 63 | "configMapKeyRef": { 64 | "name": "conjur", 65 | "key": "CONJUR_ACCOUNT" 66 | } 67 | } 68 | }, 69 | { 70 | "name": "CONJUR_APPLIANCE_URL", 71 | "valueFrom": { 72 | "configMapKeyRef": { 73 | "name": "conjur", 74 | "key": "CONJUR_APPLIANCE_URL" 75 | } 76 | } 77 | }, 78 | { 79 | "name": "CONJUR_AUTHN_LOGIN", 80 | "valueFrom": { 81 | "configMapKeyRef": { 82 | "name": "conjur", 83 | "key": "CONJUR_AUTHN_LOGIN" 84 | } 85 | } 86 | }, 87 | { 88 | "name": "CONJUR_AUTHN_TOKEN_FILE", 89 | "value": "/run/conjur/conjur-access-token" 90 | }, 91 | { 92 | "name": "CONJUR_AUTHN_URL", 93 | "valueFrom": { 94 | "configMapKeyRef": { 95 | "name": "conjur", 96 | "key": "CONJUR_AUTHN_URL" 97 | } 98 | } 99 | }, 100 | { 101 | "name": "CONJUR_SSL_CERTIFICATE", 102 | "valueFrom": { 103 | "configMapKeyRef": { 104 | "name": "conjur", 105 | "key": "CONJUR_SSL_CERTIFICATE" 106 | } 107 | } 108 | }, 109 | { 110 | "name": "CONJUR_VERSION", 111 | "valueFrom": { 112 | "configMapKeyRef": { 113 | "name": "conjur", 114 | "key": "CONJUR_VERSION" 115 | } 116 | } 117 | }, 118 | { 119 | "name": "CONTAINER_MODE", 120 | "value": "sidecar" 121 | }, 122 | { 123 | "name": "MY_POD_IP", 124 | "valueFrom": { 125 | "fieldRef": { 126 | "fieldPath": "status.podIP" 127 | } 128 | } 129 | }, 130 | { 131 | "name": "MY_POD_NAME", 132 | "valueFrom": { 133 | "fieldRef": { 134 | "fieldPath": "metadata.name" 135 | } 136 | } 137 | }, 138 | { 139 | "name": "MY_POD_NAMESPACE", 140 | "valueFrom": { 141 | "fieldRef": { 142 | "fieldPath": "metadata.namespace" 143 | } 144 | } 145 | } 146 | ], 147 | "resources": {}, 148 | "volumeMounts": [ 149 | { 150 | "name": "conjur-access-token", 151 | "mountPath": "/run/conjur" 152 | } 153 | ], 154 | "imagePullPolicy": "Always" 155 | } 156 | ] 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /pkg/inject/testdata/secretless-annotated-pod-with-image.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/conjurAuthConfig": "conjur", 10 | "conjur.org/conjurConnConfig": "conjur", 11 | "conjur.org/secretless-config": "secretless-config", 12 | "conjur.org/inject": "true", 13 | "conjur.org/inject-type": "secretless", 14 | "conjur.org/container-image": "cyberark/secretless-broker:12345" 15 | } 16 | }, 17 | "spec": { 18 | "volumes": [ 19 | { 20 | "name": "default-token-tq5lq", 21 | "secret": { 22 | "secretName": "default-token-tq5lq" 23 | } 24 | } 25 | ], 26 | "containers": [ 27 | { 28 | "name": "nginx-1", 29 | "image": "nginx:1.7.9", 30 | "volumeMounts": [ 31 | { 32 | "name": "default-token-tq5lq", 33 | "readOnly": true, 34 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 35 | } 36 | ] 37 | }, 38 | { 39 | "name": "nginx-2", 40 | "image": "nginx:1.7.9", 41 | "volumeMounts": [ 42 | { 43 | "name": "default-token-tq5lq", 44 | "readOnly": true, 45 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pkg/inject/testdata/secretless-annotated-pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/conjurAuthConfig": "conjur", 10 | "conjur.org/conjurConnConfig": "conjur", 11 | "conjur.org/secretless-config": "secretless-config", 12 | "conjur.org/inject": "true", 13 | "conjur.org/inject-type": "secretless" 14 | } 15 | }, 16 | "spec": { 17 | "volumes": [ 18 | { 19 | "name": "default-token-tq5lq", 20 | "secret": { 21 | "secretName": "default-token-tq5lq" 22 | } 23 | } 24 | ], 25 | "containers": [ 26 | { 27 | "name": "nginx-1", 28 | "image": "nginx:1.7.9", 29 | "volumeMounts": [ 30 | { 31 | "name": "default-token-tq5lq", 32 | "readOnly": true, 33 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 34 | } 35 | ] 36 | }, 37 | { 38 | "name": "nginx-2", 39 | "image": "nginx:1.7.9", 40 | "volumeMounts": [ 41 | { 42 | "name": "default-token-tq5lq", 43 | "readOnly": true, 44 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 45 | } 46 | ] 47 | } 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/inject/testdata/secretless-mutated-pod-with-image.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/status": "injected" 10 | } 11 | }, 12 | "spec": { 13 | "volumes": [ 14 | { 15 | "name": "default-token-tq5lq", 16 | "secret": { 17 | "secretName": "default-token-tq5lq" 18 | } 19 | }, 20 | { 21 | "name": "secretless-config", 22 | "configMap": { 23 | "name": "secretless-config" 24 | } 25 | } 26 | ], 27 | "containers": [ 28 | { 29 | "name": "nginx-1", 30 | "image": "nginx:1.7.9", 31 | "volumeMounts": [ 32 | { 33 | "name": "default-token-tq5lq", 34 | "readOnly": true, 35 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 36 | } 37 | ] 38 | }, 39 | { 40 | "name": "nginx-2", 41 | "image": "nginx:1.7.9", 42 | "volumeMounts": [ 43 | { 44 | "name": "default-token-tq5lq", 45 | "readOnly": true, 46 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 47 | } 48 | ] 49 | }, 50 | { 51 | "name": "secretless", 52 | "image": "cyberark/secretless-broker:12345", 53 | "args": [ 54 | "-config-mgr", 55 | "configfile#/etc/secretless/secretless.yml" 56 | ], 57 | "env": [ 58 | { 59 | "name": "CONJUR_ACCOUNT", 60 | "valueFrom": { 61 | "configMapKeyRef": { 62 | "name": "conjur", 63 | "key": "CONJUR_ACCOUNT" 64 | } 65 | } 66 | }, 67 | { 68 | "name": "CONJUR_APPLIANCE_URL", 69 | "valueFrom": { 70 | "configMapKeyRef": { 71 | "name": "conjur", 72 | "key": "CONJUR_APPLIANCE_URL" 73 | } 74 | } 75 | }, 76 | { 77 | "name": "CONJUR_AUTHN_LOGIN", 78 | "valueFrom": { 79 | "configMapKeyRef": { 80 | "name": "conjur", 81 | "key": "CONJUR_AUTHN_LOGIN" 82 | } 83 | } 84 | }, 85 | { 86 | "name": "CONJUR_AUTHN_URL", 87 | "valueFrom": { 88 | "configMapKeyRef": { 89 | "name": "conjur", 90 | "key": "CONJUR_AUTHN_URL" 91 | } 92 | } 93 | }, 94 | { 95 | "name": "CONJUR_SSL_CERTIFICATE", 96 | "valueFrom": { 97 | "configMapKeyRef": { 98 | "name": "conjur", 99 | "key": "CONJUR_SSL_CERTIFICATE" 100 | } 101 | } 102 | }, 103 | { 104 | "name": "CONJUR_VERSION", 105 | "valueFrom": { 106 | "configMapKeyRef": { 107 | "name": "conjur", 108 | "key": "CONJUR_VERSION" 109 | } 110 | } 111 | }, 112 | { 113 | "name": "MY_POD_IP", 114 | "valueFrom": { 115 | "fieldRef": { 116 | "fieldPath": "status.podIP" 117 | } 118 | } 119 | }, 120 | { 121 | "name": "MY_POD_NAME", 122 | "valueFrom": { 123 | "fieldRef": { 124 | "fieldPath": "metadata.name" 125 | } 126 | } 127 | }, 128 | { 129 | "name": "MY_POD_NAMESPACE", 130 | "valueFrom": { 131 | "fieldRef": { 132 | "fieldPath": "metadata.namespace" 133 | } 134 | } 135 | } 136 | ], 137 | "resources": {}, 138 | "volumeMounts": [ 139 | { 140 | "name": "default-token-tq5lq", 141 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", 142 | "readOnly": true 143 | }, 144 | { 145 | "name": "secretless-config", 146 | "mountPath": "/etc/secretless", 147 | "readOnly": true 148 | } 149 | ], 150 | "imagePullPolicy": "Always" 151 | } 152 | ] 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /pkg/inject/testdata/secretless-mutated-pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/status": "injected" 10 | } 11 | }, 12 | "spec": { 13 | "volumes": [ 14 | { 15 | "name": "default-token-tq5lq", 16 | "secret": { 17 | "secretName": "default-token-tq5lq" 18 | } 19 | }, 20 | { 21 | "name": "secretless-config", 22 | "configMap": { 23 | "name": "secretless-config" 24 | } 25 | } 26 | ], 27 | "containers": [ 28 | { 29 | "name": "nginx-1", 30 | "image": "nginx:1.7.9", 31 | "volumeMounts": [ 32 | { 33 | "name": "default-token-tq5lq", 34 | "readOnly": true, 35 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 36 | } 37 | ] 38 | }, 39 | { 40 | "name": "nginx-2", 41 | "image": "nginx:1.7.9", 42 | "volumeMounts": [ 43 | { 44 | "name": "default-token-tq5lq", 45 | "readOnly": true, 46 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 47 | } 48 | ] 49 | }, 50 | { 51 | "name": "secretless", 52 | "image": "secretless-image", 53 | "args": [ 54 | "-config-mgr", 55 | "configfile#/etc/secretless/secretless.yml" 56 | ], 57 | "env": [ 58 | { 59 | "name": "CONJUR_ACCOUNT", 60 | "valueFrom": { 61 | "configMapKeyRef": { 62 | "name": "conjur", 63 | "key": "CONJUR_ACCOUNT" 64 | } 65 | } 66 | }, 67 | { 68 | "name": "CONJUR_APPLIANCE_URL", 69 | "valueFrom": { 70 | "configMapKeyRef": { 71 | "name": "conjur", 72 | "key": "CONJUR_APPLIANCE_URL" 73 | } 74 | } 75 | }, 76 | { 77 | "name": "CONJUR_AUTHN_LOGIN", 78 | "valueFrom": { 79 | "configMapKeyRef": { 80 | "name": "conjur", 81 | "key": "CONJUR_AUTHN_LOGIN" 82 | } 83 | } 84 | }, 85 | { 86 | "name": "CONJUR_AUTHN_URL", 87 | "valueFrom": { 88 | "configMapKeyRef": { 89 | "name": "conjur", 90 | "key": "CONJUR_AUTHN_URL" 91 | } 92 | } 93 | }, 94 | { 95 | "name": "CONJUR_SSL_CERTIFICATE", 96 | "valueFrom": { 97 | "configMapKeyRef": { 98 | "name": "conjur", 99 | "key": "CONJUR_SSL_CERTIFICATE" 100 | } 101 | } 102 | }, 103 | { 104 | "name": "CONJUR_VERSION", 105 | "valueFrom": { 106 | "configMapKeyRef": { 107 | "name": "conjur", 108 | "key": "CONJUR_VERSION" 109 | } 110 | } 111 | }, 112 | { 113 | "name": "MY_POD_IP", 114 | "valueFrom": { 115 | "fieldRef": { 116 | "fieldPath": "status.podIP" 117 | } 118 | } 119 | }, 120 | { 121 | "name": "MY_POD_NAME", 122 | "valueFrom": { 123 | "fieldRef": { 124 | "fieldPath": "metadata.name" 125 | } 126 | } 127 | }, 128 | { 129 | "name": "MY_POD_NAMESPACE", 130 | "valueFrom": { 131 | "fieldRef": { 132 | "fieldPath": "metadata.namespace" 133 | } 134 | } 135 | } 136 | ], 137 | "resources": {}, 138 | "volumeMounts": [ 139 | { 140 | "name": "default-token-tq5lq", 141 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", 142 | "readOnly": true 143 | }, 144 | { 145 | "name": "secretless-config", 146 | "mountPath": "/etc/secretless", 147 | "readOnly": true 148 | } 149 | ], 150 | "imagePullPolicy": "Always" 151 | } 152 | ] 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /pkg/inject/testdata/secrets-provider-annotated-pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/inject": "true", 10 | "conjur.org/inject-type": "secrets-provider", 11 | "conjur.org/container-name" : "secrets-provider-name", 12 | "conjur.org/container-mode": "sidecar", 13 | "conjur.org/secrets-destination": "file", 14 | "conjur.org/conjur-inject-volumes": "nginx-1", 15 | "my-company": "my-project" 16 | } 17 | }, 18 | "spec": { 19 | "volumes": [ 20 | { 21 | "name": "default-token-tq5lq", 22 | "secret": { 23 | "secretName": "default-token-tq5lq" 24 | } 25 | } 26 | ], 27 | "containers": [ 28 | { 29 | "name": "nginx-1", 30 | "image": "nginx:1.7.9", 31 | "volumeMounts": [ 32 | { 33 | "name": "default-token-tq5lq", 34 | "readOnly": true, 35 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 36 | } 37 | ] 38 | } 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/inject/testdata/secrets-provider-init-annotated-pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/inject": "true", 10 | "conjur.org/inject-type": "secrets-provider", 11 | "conjur.org/container-name" : "secrets-provider-name", 12 | "conjur.org/container-mode": "init", 13 | "conjur.org/secrets-destination": "file", 14 | "conjur.org/conjur-inject-volumes": "nginx-1", 15 | "my-company": "my-project" 16 | } 17 | }, 18 | "spec": { 19 | "volumes": [ 20 | { 21 | "name": "default-token-tq5lq", 22 | "secret": { 23 | "secretName": "default-token-tq5lq" 24 | } 25 | } 26 | ], 27 | "containers": [ 28 | { 29 | "name": "nginx-1", 30 | "image": "nginx:1.7.9", 31 | "volumeMounts": [ 32 | { 33 | "name": "default-token-tq5lq", 34 | "readOnly": true, 35 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 36 | } 37 | ] 38 | } 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/inject/testdata/secrets-provider-init-mutated-pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/container-mode": "init", 10 | "conjur.org/secrets-destination": "file", 11 | "my-company": "my-project", 12 | "conjur.org/status": "injected" 13 | } 14 | }, 15 | "spec": { 16 | "volumes": [ 17 | { 18 | "name": "default-token-tq5lq", 19 | "secret": { 20 | "secretName": "default-token-tq5lq" 21 | } 22 | }, 23 | { 24 | "name": "podinfo", 25 | "downwardAPI": { 26 | "items": [ 27 | { 28 | "path": "annotations", 29 | "fieldRef": { 30 | "fieldPath": "metadata.annotations" 31 | } 32 | } 33 | ] 34 | } 35 | }, 36 | { 37 | "name": "conjur-status", 38 | "emptyDir": { 39 | "medium": "Memory" 40 | } 41 | }, 42 | { 43 | "name": "conjur-secrets", 44 | "emptyDir": { 45 | "medium": "Memory" 46 | } 47 | } 48 | ], 49 | "containers": [ 50 | { 51 | "name": "nginx-1", 52 | "image": "nginx:1.7.9", 53 | "volumeMounts": [ 54 | { 55 | "name": "default-token-tq5lq", 56 | "readOnly": true, 57 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 58 | }, 59 | { 60 | "name": "conjur-status", 61 | "mountPath": "/conjur/status" 62 | }, 63 | { 64 | "name": "conjur-secrets", 65 | "mountPath": "/conjur/secrets" 66 | } 67 | ] 68 | } 69 | ], 70 | "initContainers": [ 71 | { 72 | "name": "secrets-provider-name", 73 | "image": "secrets-provider-image", 74 | "resources": {}, 75 | "imagePullPolicy": "Always", 76 | "env": [ 77 | { 78 | "name": "MY_POD_NAME", 79 | "valueFrom": { 80 | "fieldRef": { 81 | "fieldPath": "metadata.name" 82 | } 83 | } 84 | }, 85 | { 86 | "name": "MY_POD_NAMESPACE", 87 | "valueFrom": { 88 | "fieldRef": { 89 | "fieldPath": "metadata.namespace" 90 | } 91 | } 92 | }, 93 | { 94 | "name": "CONJUR_ACCOUNT", 95 | "value": "myConjurAccount" 96 | }, 97 | { 98 | "name": "CONJUR_APPLIANCE_URL", 99 | "value": "https://conjur-oss.conjur-oss.svc.cluster.local" 100 | }, 101 | { 102 | "name": "CONJUR_AUTHENTICATOR_ID", 103 | "value": "my-authenticator-id" 104 | }, 105 | { 106 | "name": "CONJUR_AUTHN_URL", 107 | "value": "https://conjur-oss.conjur-oss.svc.cluster.local/authn-k8s/my-authenticator-id" 108 | }, 109 | { 110 | "name": "CONJUR_SSL_CERTIFICATE", 111 | "value": "-----BEGIN CERTIFICATE-----tVw0ZnjsOV2ZeIBRalX/72RplPzkmWKAw==\n-----END CERTIFICATE-----\n" 112 | } 113 | ], 114 | "volumeMounts": [ 115 | { 116 | "name": "podinfo", 117 | "readOnly": true, 118 | "mountPath": "/conjur/podinfo" 119 | }, 120 | { 121 | "name": "conjur-status", 122 | "mountPath": "/conjur/status" 123 | }, 124 | { 125 | "name": "conjur-secrets", 126 | "mountPath": "/conjur/secrets" 127 | } 128 | ] 129 | } 130 | ] 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /pkg/inject/testdata/secrets-provider-mutated-pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generateName": "nginx-deployment-6c54bd5869-", 4 | "labels": { 5 | "app": "nginx", 6 | "pod-template-hash": "2710681425" 7 | }, 8 | "annotations": { 9 | "conjur.org/container-mode": "sidecar", 10 | "conjur.org/secrets-destination": "file", 11 | "my-company": "my-project", 12 | "conjur.org/status": "injected" 13 | } 14 | }, 15 | "spec": { 16 | "volumes": [ 17 | { 18 | "name": "default-token-tq5lq", 19 | "secret": { 20 | "secretName": "default-token-tq5lq" 21 | } 22 | }, 23 | { 24 | "name": "podinfo", 25 | "downwardAPI": { 26 | "items": [ 27 | { 28 | "path": "annotations", 29 | "fieldRef": { 30 | "fieldPath": "metadata.annotations" 31 | } 32 | } 33 | ] 34 | } 35 | }, 36 | { 37 | "name": "conjur-status", 38 | "emptyDir": { 39 | "medium": "Memory" 40 | } 41 | }, 42 | { 43 | "name": "conjur-secrets", 44 | "emptyDir": { 45 | "medium": "Memory" 46 | } 47 | } 48 | ], 49 | "containers": [ 50 | { 51 | "name": "nginx-1", 52 | "image": "nginx:1.7.9", 53 | "volumeMounts": [ 54 | { 55 | "name": "default-token-tq5lq", 56 | "readOnly": true, 57 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" 58 | }, 59 | { 60 | "name": "conjur-status", 61 | "mountPath": "/conjur/status" 62 | }, 63 | { 64 | "name": "conjur-secrets", 65 | "mountPath": "/conjur/secrets" 66 | } 67 | ] 68 | }, 69 | { 70 | "name": "secrets-provider-name", 71 | "image": "secrets-provider-image", 72 | "resources": {}, 73 | "imagePullPolicy": "Always", 74 | "env": [ 75 | { 76 | "name": "MY_POD_NAME", 77 | "valueFrom": { 78 | "fieldRef": { 79 | "fieldPath": "metadata.name" 80 | } 81 | } 82 | }, 83 | { 84 | "name": "MY_POD_NAMESPACE", 85 | "valueFrom": { 86 | "fieldRef": { 87 | "fieldPath": "metadata.namespace" 88 | } 89 | } 90 | }, 91 | { 92 | "name": "CONJUR_ACCOUNT", 93 | "value": "myConjurAccount" 94 | }, 95 | { 96 | "name": "CONJUR_APPLIANCE_URL", 97 | "value": "https://conjur-oss.conjur-oss.svc.cluster.local" 98 | }, 99 | { 100 | "name": "CONJUR_AUTHENTICATOR_ID", 101 | "value": "my-authenticator-id" 102 | }, 103 | { 104 | "name": "CONJUR_AUTHN_URL", 105 | "value": "https://conjur-oss.conjur-oss.svc.cluster.local/authn-k8s/my-authenticator-id" 106 | }, 107 | { 108 | "name": "CONJUR_SSL_CERTIFICATE", 109 | "value": "-----BEGIN CERTIFICATE-----tVw0ZnjsOV2ZeIBRalX/72RplPzkmWKAw==\n-----END CERTIFICATE-----\n" 110 | } 111 | ], 112 | "volumeMounts": [ 113 | { 114 | "name": "podinfo", 115 | "readOnly": true, 116 | "mountPath": "/conjur/podinfo" 117 | }, 118 | { 119 | "name": "conjur-status", 120 | "mountPath": "/conjur/status" 121 | }, 122 | { 123 | "name": "conjur-secrets", 124 | "mountPath": "/conjur/secrets" 125 | } 126 | ] 127 | } 128 | ] 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /pkg/inject/utils_test.go: -------------------------------------------------------------------------------- 1 | package inject 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "text/template" 8 | 9 | jsonpatch "github.com/evanphx/json-patch" 10 | ) 11 | 12 | // applyPatchToAdmissionRequest runs an AdmissionRequest (wrapped in an AdmissionReview) 13 | // through the sidecar-injector logic to extract a mutation patch, it then applies this 14 | // patch to the origin Pod template spec to return the mutated Pod template spec. 15 | func applyPatchToAdmissionRequest(reviewRequestBytes []byte) ([]byte, error) { 16 | req, err := NewAdmissionRequest(reviewRequestBytes) 17 | if err != nil { 18 | return nil, err 19 | } 20 | admissionRes := HandleAdmissionRequest( 21 | SidecarInjectorConfig{ 22 | SecretlessContainerImage: "secretless-image", 23 | AuthenticatorContainerImage: "authenticator-image", 24 | SecretsProviderContainerImage: "secrets-provider-image", 25 | }, 26 | req, 27 | ) 28 | 29 | patch, err := jsonpatch.DecodePatch(admissionRes.Patch) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return patch.Apply(req.Object.Raw) 35 | } 36 | 37 | // newTestAdmissionRequest creates an Admission Request (wrapped in a Admission Review). 38 | // This is done by embedding a pod template spec, whose path is an argument, inside the 39 | // shell of an example Admission Request. This method simplifies generating test 40 | // Admission Requests. 41 | func newTestAdmissionRequest(podTemplateSpecPath string) ([]byte, error) { 42 | t := template.Must( 43 | template.ParseFiles( 44 | "./testdata/authenticator-admission-request.tmpl.json", 45 | ), 46 | ) 47 | 48 | pod, err := ioutil.ReadFile(podTemplateSpecPath) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | var reqJSON bytes.Buffer 54 | 55 | err = t.Execute(&reqJSON, string(pod)) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | var reqPrettyJSON bytes.Buffer 61 | err = json.Indent(&reqPrettyJSON, reqJSON.Bytes(), "", " ") 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | return reqPrettyJSON.Bytes(), nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import "fmt" 4 | 5 | // gitVersion is a SemVer that captures the baked-in version of the sidecar-injector. 6 | const gitVersion = "0.2.0" 7 | 8 | // gitCommitShort denotes the specific build type for the sidecar-injector. It defaults to the 9 | // special value 'dev'. It is expected to be replaced by the compile-time value of the 10 | // short sha1 from git of the build commit, output of $(git rev-parse --short HEAD). 11 | var gitCommitShort = "dev" 12 | 13 | // Get returns the user-visible aggregation of gitVersion and gitCommitShort 14 | // of this codebase. 15 | func Get() string { 16 | return fmt.Sprintf("%s-%s", gitVersion, gitCommitShort) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/version/version_test.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_gitVersionIsPresent(t *testing.T) { 10 | assert.NotEmpty(t, gitVersion, "Expected gitVersion to be non-empty but got an empty value") 11 | } 12 | 13 | func Test_gitCommitShortIsPresent(t *testing.T) { 14 | assert.NotEmpty(t, gitCommitShort, "Expected gitCommitShort to be non-empty but got an empty value") 15 | } 16 | 17 | func Test_gitVersionIsCorrectFormat(t *testing.T) { 18 | assert.Regexp(t, `^[0-9]+\.[0-9]+\.[0-9]+$`, gitVersion, 19 | "Expected gitVersion to be a SemVer string") 20 | } 21 | 22 | func TestGetReturnsCorrectFormat(t *testing.T) { 23 | assert.Regexp(t, `^[0-9]+\.[0-9]+\.[0-9]+-[a-z0-9]+$`, Get(), 24 | "Get should return a '-' string") 25 | } 26 | 27 | // For now we enforce just plain lowercase alphanumerics, which matches 'dev' and sha1 28 | // hashes. 29 | func Test_gitCommitShortIsCorrectFormat(t *testing.T) { 30 | assert.Regexp(t, `^[a-z0-9]+$`, gitCommitShort, 31 | "gitCommitShort should be a strict lowercase alphanumeric string") 32 | } 33 | -------------------------------------------------------------------------------- /run-local-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run tests for cyberark sidecar-injector in a local Kubernetes-in-Docker 4 | # (KinD) environment. This test script assumes that a local KinD cluster 5 | # is up and running, and that the following client binaries are installed 6 | # locally: 7 | # - kubectl 8 | # - kubens 9 | # - helm 10 | # 11 | # These tests currently exercise the secrectless broker sidecar. 12 | 13 | set -euo pipefail 14 | cd "$(dirname "$0")" 15 | 16 | . tests/utils 17 | 18 | # Set environment variables 19 | UNIQUE_TEST_ID="$(uuidgen | tr "[:upper:]" "[:lower:]" | head -c 6 | tr -d -)" 20 | export UNIQUE_TEST_ID 21 | export SECRETLESS_CRD_SUFFIX="${UNIQUE_TEST_ID}" 22 | export SIDECAR_IMAGE="sidecar-injector:latest" 23 | export TEST_NAMESPACE="sci-${UNIQUE_TEST_ID}" 24 | 25 | function testInjection() { 26 | set -x 27 | 28 | # Load the local sidecar-injector image to the local KinD cluster node(s). 29 | # (Note that for this to work, Kubernetes Deployments must use an 30 | # imagePullPolicy of 'Never' for this image so that Kubernetes never tries 31 | # to pull images from the Docker registry, and instead always uses images 32 | # from each node's local image cache.) 33 | kind load docker-image "${SIDECAR_IMAGE}" 34 | 35 | export SECRETLESS_CRD_SUFFIX="${UNIQUE_TEST_ID}"; 36 | 37 | # create test namespace 38 | kubectl create namespace "${TEST_NAMESPACE}" 39 | 40 | # switch to test namespace 41 | kubectl config set-context --current --namespace="${TEST_NAMESPACE}" 42 | 43 | # run CRD tests 44 | cd tests; 45 | export K8S_PLATFORM="kind" 46 | # TODO: Rename this script since it isn't always run inside a Docker 47 | # container (e.g. it's being run on the local host here). 48 | ./tests_within_docker 49 | } 50 | 51 | function main() { 52 | announce 'Build Sidecar Injector Image' 53 | ./bin/build latest 54 | 55 | docker inspect sidecar-injector:latest >/dev/null 2>&1 || { 56 | echo "ERROR: sidecar-injector:latest must exist locally for test to run." 57 | exit 1 58 | } 59 | 60 | announce 'Test Sidecar Injection with Secretless configured via CRD' 61 | testInjection 62 | } 63 | 64 | main 65 | -------------------------------------------------------------------------------- /run-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run tests for cyberark sidecar-injector. 4 | # These tests currently exercise the secretless broker sidecar. 5 | 6 | set -euo pipefail 7 | cd "$(dirname "$0")" 8 | 9 | . tests/utils 10 | 11 | # Set environment variables 12 | UNIQUE_TEST_ID="$(uuidgen | tr "[:upper:]" "[:lower:]" | head -c 6 | tr -d -)" 13 | export UNIQUE_TEST_ID 14 | export SECRETLESS_CRD_SUFFIX="${UNIQUE_TEST_ID}" 15 | export SECRETLESS_CRD_SUFFIX 16 | export SIDECAR_IMAGE="${DOCKER_REGISTRY_PATH}/sidecar-injector:${UNIQUE_TEST_ID}" 17 | export TEST_NAMESPACE="sci-${UNIQUE_TEST_ID}" 18 | 19 | 20 | function testInjection() { 21 | runDockerCommand " 22 | set -x 23 | # push the local sidecar-injector image to the remote DOCKER_REGISTRY_PATH 24 | docker tag 'sidecar-injector:latest' '${SIDECAR_IMAGE}' > /dev/null; 25 | docker push '${SIDECAR_IMAGE}' > /dev/null; 26 | 27 | export SECRETLESS_CRD_SUFFIX='${UNIQUE_TEST_ID}'; 28 | 29 | # create test namespace 30 | kubectl create namespace '${TEST_NAMESPACE}' 31 | 32 | # switch to test namespace 33 | kubens '${TEST_NAMESPACE}' 34 | 35 | # run CRD tests 36 | cd tests; 37 | export SIDECAR_IMAGE='${SIDECAR_IMAGE}'; 38 | ./tests_within_docker 39 | " 40 | } 41 | 42 | function main() { 43 | announce 'Build Sidecar Injector Image' 44 | ./bin/build latest 45 | 46 | docker inspect sidecar-injector:latest >/dev/null 2>&1 || { 47 | echo "ERROR: sidecar-injector:latest must exist locally for test to run." 48 | exit 1 49 | } 50 | 51 | announce 'Build Test Execution Docker Image' 52 | prepareTestEnvironment 53 | 54 | announce 'Test Sidecar Injection with Secretless configured via CRD' 55 | testInjection 56 | } 57 | 58 | main 59 | -------------------------------------------------------------------------------- /tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM google/cloud-sdk:latest 2 | 3 | ARG HELM_VERSION 4 | ARG KUBECTL_VERSION=1.22.0 5 | 6 | RUN mkdir -p /src 7 | WORKDIR /src 8 | 9 | # Install Helm client 10 | RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 11 | RUN chmod 700 get_helm.sh 12 | RUN ./get_helm.sh --no-sudo --version ${HELM_VERSION:-v3.7.0} 13 | 14 | # Install Docker client 15 | RUN apt-get update -y && \ 16 | apt-get install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common wget && \ 17 | curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | apt-key add - && \ 18 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable" && \ 19 | apt-get update && \ 20 | apt-get install -y docker-ce && \ 21 | rm -rf /var/lib/apt/lists/* 22 | 23 | # Install kubectl CLI 24 | RUN wget -O /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v"${KUBECTL_VERSION}"/bin/linux/amd64/kubectl && \ 25 | chmod +x /usr/local/bin/kubectl 26 | 27 | # Install kubectx and kubens 28 | RUN wget -O /usr/local/bin/kubectx https://raw.githubusercontent.com/ahmetb/kubectx/master/kubectx && \ 29 | chmod +x /usr/local/bin/kubectx 30 | RUN wget -O /usr/local/bin/kubens https://raw.githubusercontent.com/ahmetb/kubectx/master/kubens && \ 31 | chmod +x /usr/local/bin/kubens 32 | -------------------------------------------------------------------------------- /tests/echoserver.yaml.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # returns current namespace if available, otherwise returns 'default' 4 | current_namespace() { 5 | cur_ctx="$(kubectl config current-context)" || exit_err "error getting current context" 6 | ns="$(kubectl config view -o=jsonpath="{.contexts[?(@.name==\"${cur_ctx}\")].context.namespace}")" \ 7 | || exit_err "error getting current namespace" 8 | 9 | if [[ -z "${ns}" ]]; then 10 | echo "default" 11 | else 12 | echo "${ns}" 13 | fi 14 | } 15 | 16 | # list of api groups that contain configurations 17 | api_groups(){ 18 | kubectl api-resources \ 19 | |awk '/sbconfig/{print " - "$3}' 20 | } 21 | 22 | cat << EOL 23 | kind: ClusterRole 24 | apiVersion: rbac.authorization.k8s.io/v1 25 | metadata: 26 | name: secretless-crd-${SECRETLESS_CRD_SUFFIX} 27 | rules: 28 | - apiGroups: 29 | - apiextensions.k8s.io 30 | resources: 31 | - customresourcedefinitions 32 | verbs: 33 | - create 34 | - get 35 | - watch 36 | - list 37 | - apiGroups: [""] 38 | resources: 39 | - namespaces 40 | verbs: 41 | - get 42 | - list 43 | - watch 44 | - apiGroups: 45 | - secretless${SECRETLESS_CRD_SUFFIX}.io 46 | $(api_groups) 47 | resources: 48 | - configurations 49 | verbs: 50 | - get 51 | - list 52 | - watch 53 | 54 | --- 55 | apiVersion: v1 56 | kind: ServiceAccount 57 | metadata: 58 | name: secretless-crd-${SECRETLESS_CRD_SUFFIX} 59 | 60 | --- 61 | kind: ClusterRoleBinding 62 | apiVersion: rbac.authorization.k8s.io/v1 63 | metadata: 64 | name: secretless-crd-${SECRETLESS_CRD_SUFFIX} 65 | subjects: 66 | - kind: ServiceAccount 67 | name: secretless-crd-${SECRETLESS_CRD_SUFFIX} 68 | namespace: $(current_namespace) 69 | roleRef: 70 | kind: ClusterRole 71 | name: secretless-crd-${SECRETLESS_CRD_SUFFIX} 72 | apiGroup: rbac.authorization.k8s.io 73 | 74 | --- 75 | apiVersion: "secretless${SECRETLESS_CRD_SUFFIX}.io/v1" 76 | kind: "Configuration" 77 | metadata: 78 | name: crd-basic-auth-proxy 79 | spec: 80 | listeners: 81 | - name: http_config_1_listener 82 | protocol: http 83 | address: 0.0.0.0:8000 84 | 85 | handlers: 86 | - name: http_config_1_handler 87 | type: basic_auth 88 | listener: http_config_1_listener 89 | match: 90 | - ^http.* 91 | credentials: 92 | - name: username 93 | provider: literal 94 | id: "username" 95 | - name: password 96 | provider: literal 97 | id: "password" 98 | 99 | --- 100 | apiVersion: apps/v1 101 | kind: Deployment 102 | metadata: 103 | name: sb-sci-echoserver 104 | spec: 105 | replicas: 1 106 | selector: 107 | matchLabels: 108 | app: sb-sci-echoserver 109 | template: 110 | metadata: 111 | labels: 112 | app: sb-sci-echoserver 113 | annotations: 114 | conjur.org/inject: "yes" 115 | conjur.org/secretless-config: "k8s/crd#crd-basic-auth-proxy" 116 | conjur.org/secretless-CRD-suffix: ${SECRETLESS_CRD_SUFFIX} 117 | conjur.org/inject-type: "secretless" 118 | 119 | spec: 120 | serviceAccountName: secretless-crd-${SECRETLESS_CRD_SUFFIX} 121 | containers: 122 | - name: echo-server 123 | image: gcr.io/google_containers/echoserver:1.10 124 | imagePullPolicy: Always 125 | # Secretless container to be added here by sidecar injector 126 | EOL 127 | -------------------------------------------------------------------------------- /tests/platform_login: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | function main() { 6 | echo "Platform Login" 7 | gcloud auth activate-service-account \ 8 | --key-file "${GCLOUD_SERVICE_KEY}" 9 | 10 | gcloud container clusters get-credentials \ 11 | "${GCLOUD_CLUSTER_NAME}" \ 12 | --zone "${GCLOUD_ZONE}" \ 13 | --project "${GCLOUD_PROJECT_NAME}" 14 | 15 | docker login "${DOCKER_REGISTRY_URL}" \ 16 | -u oauth2accesstoken \ 17 | -p "$(gcloud auth print-access-token)" 18 | 19 | echo "Platform Login Complete" 20 | } 21 | 22 | main 23 | -------------------------------------------------------------------------------- /tests/secrets.yml: -------------------------------------------------------------------------------- 1 | GCLOUD_CLUSTER_NAME: !var ci/gke/rapid/cluster-name 2 | GCLOUD_ZONE: !var ci/gke/zone 3 | GCLOUD_PROJECT_NAME: !var ci/gke/project-name 4 | GCLOUD_SERVICE_KEY: !var:file ci/gke/service-key 5 | 6 | DOCKER_REGISTRY_URL: us.gcr.io 7 | DOCKER_REGISTRY_PATH: us.gcr.io/refreshing-mark-284016 8 | -------------------------------------------------------------------------------- /tests/tests_within_docker: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is run within a docker container that has access 4 | # to GKE credentials and configured clients. 5 | 6 | # 1. Deploys the sidecar injector 7 | # 2. Deploys a test app (echo server), which is configured to have 8 | # secretless broker (SB) injected. The injected SB is configured via a CRD 9 | # (custom resource definition). SB is configured to proxy http requests to 10 | # the echo server, adding in a basic auth header. 11 | # 3. A request is made to the echo server with SB as the proxy, the test passes 12 | # if a basic auth header (inserted by SB) is detected in the response from 13 | # the echo server. 14 | 15 | set -exu 16 | set -o pipefail 17 | 18 | . ./utils 19 | 20 | # setup environment variables 21 | log=log.txt 22 | : >${log} 23 | export SECRETLESS_CRD_SUFFIX=${SECRETLESS_CRD_SUFFIX-} 24 | HELM_DEPLOYMENT="sci-${UNIQUE_TEST_ID}" 25 | 26 | # Clean up when script completes 27 | function cleanup() { 28 | announce 'Cleaning up test environment' 29 | 30 | # Delete image from GCR 31 | if [[ "$K8S_PLATFORM" == "gke" ]]; then 32 | gcloud container images delete --force-delete-tags -q "${SIDECAR_IMAGE}" ||: 33 | fi 34 | 35 | # Delete sci helm deployment 36 | if helm list --all --namespace "${I_NAMESPACE}" | grep -q "${HELM_DEPLOYMENT}"; then 37 | helm uninstall --namespace "${I_NAMESPACE}" "${HELM_DEPLOYMENT}" 38 | fi 39 | 40 | # Delete k8s test namespaces 41 | kubectl delete namespace "${TEST_NAMESPACE}" \ 42 | --wait=true --ignore-not-found=true 43 | kubectl delete namespace "i-${TEST_NAMESPACE}" \ 44 | --wait=true --ignore-not-found=true 45 | 46 | # Make sure k8s cluster-scoped resources are cleaned up 47 | kubectl delete crd "configurations.secretless${SECRETLESS_CRD_SUFFIX}.io" \ 48 | --wait=true --ignore-not-found=true & 49 | kubectl delete mutatingwebhookconfiguration \ 50 | cyberark-sidecar-injector.i-"${TEST_NAMESPACE}" \ 51 | --wait=true --ignore-not-found=true & 52 | kubectl delete clusterrole "cyberark-sidecar-injector.${I_NAMESPACE}" \ 53 | --wait=true --ignore-not-found=true & 54 | kubectl delete clusterrolebinding "cyberark-sidecar-injector.${I_NAMESPACE}" \ 55 | --wait=true --ignore-not-found=true & 56 | kubectl delete clusterrole "secretless-crd-${SECRETLESS_CRD_SUFFIX}" \ 57 | --wait=true --ignore-not-found=true & 58 | kubectl delete clusterrolebinding "secretless-crd-${SECRETLESS_CRD_SUFFIX}" \ 59 | --wait=true --ignore-not-found=true & 60 | wait 61 | } 62 | 63 | exit_trap() { 64 | cleanup 65 | printf "\n--------------------- \n\n" 66 | printf "\n- exited\n\n" 67 | echo 1>&2 "${1:-}" 68 | if [[ -e ${log} ]]; then 69 | printf "\n-- last logs\n\n" 70 | cat ${log} 71 | rm -rf ${log} 72 | fi 73 | } 74 | trap exit_trap HUP INT QUIT TERM EXIT ERR 75 | 76 | exit_err() { 77 | announce "Exiting with error: ${*}!!!!" 78 | echo -e "\n\n*** Showing all MutatingWebhookConfigurations ***" 79 | kubectl get mutatingwebhookconfigurations 80 | echo -e "\n\n*** All objects in ${I_NAMESPACE} Namespace ***" 81 | kubectl get -n "${I_NAMESPACE}" all 82 | echo -e "\n\n*** Showing all Pods in ${I_NAMESPACE} Namespace ***" 83 | kubectl get -n "${I_NAMESPACE}" pods -o yaml 84 | echo -e "\n\n*** Showing log for sidecar injector Pods ${I_NAMESPACE} Namespace ***" 85 | pod="$(kubectl get pods -n ${I_NAMESPACE} -o name)" 86 | kubectl logs -n "${I_NAMESPACE}" "$pod" 87 | echo -e "\n\n*** All objects in ${TEST_NAMESPACE} Namespace ***" 88 | kubectl get -n "${TEST_NAMESPACE}" all 89 | echo -e "\n\n*** Showing echo-server deployment ***" 90 | show_echoserver_deployment 91 | echo -e "\n\n*** Showing all Pods in ${TEST_NAMESPACE} Namespace ***" 92 | show_all_pods 93 | echo -e "\n\n*** Showing echo-server Pod ***" 94 | show_echoserver_pod 95 | echo -e "\n\n*** echoserver.yaml ***" 96 | cat echoserver.yaml 97 | echo -e "\n\n*** Secretless container logs ***" 98 | kubectl -n "${TEST_NAMESPACE}" logs "$(echoserver_pod_name)" -c secretless 99 | echo -e "\n\n*** echo-server container logs ***" 100 | kubectl -n "${TEST_NAMESPACE}" logs "$(echoserver_pod_name)" -c echo-server 101 | exit 1 102 | } 103 | 104 | function show_echoserver_deployment() { 105 | kubectl get deployments \ 106 | -n "${TEST_NAMESPACE}" \ 107 | sb-sci-echoserver \ 108 | -o yaml \ 109 | 2>${log} 110 | } 111 | 112 | function show_all_pods() { 113 | kubectl get pods \ 114 | -n "${TEST_NAMESPACE}" \ 115 | -o yaml \ 116 | 2>${log} 117 | } 118 | 119 | function show_echoserver_pod() { 120 | kubectl get pods \ 121 | -n "${TEST_NAMESPACE}" \ 122 | -l app=sb-sci-echoserver \ 123 | -o yaml \ 124 | 2>${log} 125 | } 126 | 127 | function echoserver_pod_name() { 128 | kubectl get pods \ 129 | -n "${TEST_NAMESPACE}" \ 130 | --field-selector=status.phase=Running \ 131 | -l app=sb-sci-echoserver \ 132 | -o jsonpath="{.items[0].metadata.name}" \ 133 | 2>${log} 134 | } 135 | 136 | function echoserver_pod_ready() { 137 | (kubectl -n "${TEST_NAMESPACE}" describe pod "$(echoserver_pod_name)" 2>${log} \ 138 | || echo "Ready False") | awk '/Ready/{if ($2 != "True") exit 1}' 139 | } 140 | 141 | function get_first_pod_for_app() { 142 | kubectl get \ 143 | --namespace "$2" \ 144 | po -l=app="$1" \ 145 | -o=jsonpath='{$.items[0].metadata.name}' 146 | } 147 | 148 | function curl_echoserver_via_sb() { 149 | kubectl -n "${TEST_NAMESPACE}" \ 150 | exec -i "$(echoserver_pod_name)" -c echo-server -- \ 151 | env http_proxy=localhost:8000 curl \ 152 | -v \ 153 | --connect-timeout 4 \ 154 | localhost:8080 2>${log} 155 | } 156 | function curl_echoserver() { 157 | kubectl -n "${TEST_NAMESPACE}" \ 158 | exec -i "$(echoserver_pod_name)" -c echo-server -- curl \ 159 | --connect-timeout 4 \ 160 | localhost:8080 &>${log} 161 | } 162 | 163 | function wait_for_echoserver_pod() { 164 | echo "waiting for pod to be ready" 165 | for _ in {1..60}; do 166 | echoserver_pod_ready && break 167 | printf "." 168 | sleep 2 169 | done 170 | echoserver_pod_ready || exit_err "timeout waiting for echoserver_pod_ready" 171 | 172 | for _ in {1..60}; do 173 | curl_echoserver && break 174 | printf "." 175 | sleep 2 176 | done 177 | curl_echoserver || exit_err "timeout waiting for curl_echoserver" 178 | 179 | echo "" 180 | echo "ready" 181 | echo "" 182 | } 183 | 184 | function check_sb_injected_auth_header_present_in_echoserver_response() { 185 | local username=$1 186 | local password=$2 187 | local resp 188 | resp=$(curl_echoserver_via_sb) 189 | local expected_header 190 | expected_header="authorization=Basic $(printf "%s" "${username}:${password}" | base64)" 191 | 192 | if printf "%s" "${resp}" | grep -q "${expected_header}"; then 193 | echo "test passed ✔" 194 | else 195 | echo "expected to find '${expected_header}', in response:" >${log} 196 | echo "${resp}" >>${log} 197 | exit_err "test failed ✗" 198 | fi 199 | } 200 | 201 | function deploy_echoserver() { 202 | echo "deploying echoserver test app" 203 | 204 | echo ">>--- Labeling Namespace for Sidecar Injection" 205 | kubectl label \ 206 | namespace "${TEST_NAMESPACE}" \ 207 | "cyberark-sidecar-injector-${I_NAMESPACE}=enabled" 208 | 209 | ./echoserver.yaml.sh >echoserver.yaml 2>${log} || exit_err "Failed to template echoserver.yaml" 210 | kubectl -n "${TEST_NAMESPACE}" \ 211 | apply -f echoserver.yaml 2>${log} || exit_err "Failed to deploy echoserver.yaml" 212 | 213 | 214 | echo "echoserver test app deployed" 215 | echo "" 216 | } 217 | 218 | function deploy_injector() { 219 | # /src --> cyberark/sidecar-injector 220 | pushd ../ 221 | 222 | I_NAMESPACE="i-${TEST_NAMESPACE}" 223 | cert="cyberark-sidecar-injector.${I_NAMESPACE}" 224 | 225 | echo ">>--- Ensure sidecar-injector namespace is clean" 226 | if helm list --all --namespace "${I_NAMESPACE}" | grep -q "${HELM_DEPLOYMENT}"; then 227 | helm uninstall --namespace "${I_NAMESPACE}" "${HELM_DEPLOYMENT}" 228 | fi 229 | 230 | kubectl delete namespace "${I_NAMESPACE}" --ignore-not-found=true 231 | 232 | kubectl create namespace "${I_NAMESPACE}" 233 | 234 | # Will determine if the conjur-configmap is a requirement for all sidecars 235 | # Currently only needed for Secrets Provider so adding a dummy config for the others 236 | echo ">>--- Create config map" 237 | kubectl create configmap conjur-configmap --namespace "${I_NAMESPACE}" --from-literal=authnK8sAuthenticatorID=my-authenticator-id 238 | 239 | echo ">>--- Deploy sidecar injection helm chart" 240 | 241 | helm install "${HELM_DEPLOYMENT}" helm/cyberark-sidecar-injector \ 242 | --namespace "${I_NAMESPACE}" \ 243 | --set "namespaceSelectorLabel=cyberark-sidecar-injector-${I_NAMESPACE}" \ 244 | --set "deploymentApiVersion=apps/v1" \ 245 | --set "SECRETLESS_CRD_SUFFIX=${SECRETLESS_CRD_SUFFIX}" \ 246 | --set "sidecarInjectorImage=${SIDECAR_IMAGE}" \ 247 | --set "sidecarInjectorImagePullPolicy=${INJECTOR_IMAGE_PULL_POLICY}" \ 248 | --set "caBundle=$( 249 | kubectl -n kube-system \ 250 | get configmap \ 251 | extension-apiserver-authentication \ 252 | -o=jsonpath='{.data.client-ca-file}' 253 | )" 254 | popd 255 | 256 | announce "Waiting for CSR and approving it" 257 | echo ">>--- Wait for CSR to be created" 258 | csr_found=false 259 | for _ in {1..30}; do 260 | # the yaml output doesn't include the condition field so -o jsonpath doesn't work here 261 | kubectl \ 262 | get "csr/${cert}" \ 263 | |grep Pending \ 264 | && csr_found=true \ 265 | && break 266 | sleep 1 267 | done 268 | 269 | if [ "$csr_found" = false ]; then 270 | # Display CSR for troubleshooting 271 | echo ">>--- Pending CSR not found. Displaying init container log." 272 | pod=$(get_first_pod_for_app cyberark-sidecar-injector "${I_NAMESPACE}") 273 | kubectl logs -n "${I_NAMESPACE}" "${pod}" -c init-webhook 274 | exit 1 275 | fi 276 | 277 | echo ">>--- Found CSR, aproving" 278 | kubectl certificate approve "${cert}" 279 | 280 | echo ">>--- Waiting for Sidecar Injector pod to initialise" 281 | pod=$(get_first_pod_for_app cyberark-sidecar-injector "${I_NAMESPACE}") 282 | for _ in {1..60}; do 283 | status=$(kubectl -n "${I_NAMESPACE}" get "pod/${pod}" \ 284 | -o jsonpath="{.status.phase}"||:) 285 | [[ "${status}" == "Running" ]] && break 286 | sleep 1 287 | done 288 | echo ">>--- Sidecar Injector pod running" 289 | echo ">>--- Sidecar Injector deployment complete." 290 | } 291 | 292 | function main() { 293 | echo "using SECRETLESS_CRD_SUFFIX=${SECRETLESS_CRD_SUFFIX}" 294 | echo "UTID: ${UNIQUE_TEST_ID}" 295 | export K8S_PLATFORM="${K8S_PLATFORM:-gke}" 296 | 297 | if [[ "$K8S_PLATFORM" == "kind" ]]; then 298 | INJECTOR_IMAGE_PULL_POLICY="Never" 299 | else 300 | INJECTOR_IMAGE_PULL_POLICY="Always" 301 | fi 302 | export INJECTOR_IMAGE_PULL_POLICY 303 | 304 | # deploy injector 305 | # This creates deploys a sidecar injector pod. This listens for deployment 306 | # creations and modifies them in flight to add a secretless-broker container, 307 | # if the appropriate annotations are found. 308 | announce "Deploying Sidecar Injector" 309 | pwd 310 | deploy_injector 311 | 312 | # deploy secretless deps and test app (echo server) 313 | # This function deploys resources required by secretless broker (RBAC, CRD) 314 | # but doesn't actually deploy secretless. It does create a deployment with 315 | # annotations that should cause the injector deployed in the previous step 316 | # to modify the deployment and insert a secretless broker container. 317 | # 318 | # If the sidecar injector succesfully adds the broker to the pod, 319 | # the situation will look like this: 320 | # 321 | # +-----------------------------------------+ 322 | # | Test Pod | 323 | # | | 324 | # | +-------------+ +------------+ | 325 | # | | Echo Server | | Secretless | | 326 | # | | | | Broker | | 327 | # | | +<-----+ | | 328 | # | | | 2 | | | 329 | # | | exec | | | | 330 | # +-------------------> curl +--------->+ | | 331 | # | | | 1 | | | 332 | # | | | | | | 333 | # | +-------------+ +------------+ | 334 | # | | 335 | # | | 336 | # +-----------------------------------------+ 337 | # 338 | # Test scenario: 339 | # the echo server is an app that requires basic auth, kubectl exec curl is 340 | # an that uses echo server but doesn't know the credentials. Curl makes a 341 | # request to secretless broker (1) the broker inserts a basic auth header 342 | # into the requet and proxies it to the echo server (2). Echo server 343 | # responds back. 344 | # 345 | # Echo server doesn't actually require basic auth, but because the echo 346 | # server echos it's requests, we can inspect the response from the echo 347 | # server to check that secretless broker injected the basic auth header. 348 | 349 | announce "Deploying Echoserver Test Application" 350 | deploy_echoserver 351 | wait_for_echoserver_pod 352 | 353 | # At this point everything is deployed, we need to make a request, that 354 | # will be proxied by secretless broker and check the basic auth header. 355 | announce "Verifying Deployment" 356 | check_sb_injected_auth_header_present_in_echoserver_response username password 357 | } 358 | 359 | main 360 | -------------------------------------------------------------------------------- /tests/utils: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #set -euo pipefail 4 | 5 | # Sets additional required environment variables that aren't available in the 6 | # secrets.yml file, and performs other preparatory steps 7 | function prepareTestEnvironment() { 8 | # Prepare Docker images 9 | pushd tests || exit 1 10 | docker build --rm --tag "gke-utils:latest" . >/dev/null 11 | popd || exit 1 12 | } 13 | 14 | function runDockerCommand() { 15 | docker run --rm \ 16 | -i \ 17 | -e DOCKER_REGISTRY_URL \ 18 | -e DOCKER_REGISTRY_PATH \ 19 | -e GCLOUD_SERVICE_KEY="/tmp${GCLOUD_SERVICE_KEY}" \ 20 | -e GCLOUD_CLUSTER_NAME \ 21 | -e GCLOUD_ZONE \ 22 | -e GCLOUD_PROJECT_NAME \ 23 | -e SIDECAR_IMAGE \ 24 | -e SECRETLESS_CRD_SUFFIX \ 25 | -e UNIQUE_TEST_ID \ 26 | -e TEST_NAMESPACE \ 27 | -v "${GCLOUD_SERVICE_KEY}:/tmp${GCLOUD_SERVICE_KEY}" \ 28 | -v /var/run/docker.sock:/var/run/docker.sock \ 29 | -v ~/.config:/root/.config \ 30 | -v "$PWD":/src \ 31 | -w /src \ 32 | "gke-utils:latest" \ 33 | bash -xec " 34 | pwd 35 | ./tests/platform_login > /dev/null 36 | $1 37 | " 38 | } 39 | 40 | function announce() { 41 | echo "++++++++++++++++++++++++++++++++++++++" 42 | echo "" 43 | echo "$@" 44 | echo "" 45 | echo "++++++++++++++++++++++++++++++++++++++" 46 | } 47 | --------------------------------------------------------------------------------