├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── push_pr.yml │ ├── release.yml │ └── repolinter.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── RELEASE.md ├── e2e-tests ├── k8s-e2e-bootstraping.sh ├── manifests │ ├── cert-manager-job.yaml │ └── webhook.yaml └── tests.sh └── generate_certificate.sh /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug report \U0001F41B" 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | [NOTE]: # ( ^^ Provide a general summary of the issue in the title above. ^^ ) 10 | 11 | ## Description 12 | [NOTE]: # ( Describe the problem you're encountering. ) 13 | [TIP]: # ( Do NOT share sensitive information, whether personal, proprietary, or otherwise! ) 14 | 15 | ## Expected Behavior 16 | [NOTE]: # ( Tell us what you expected to happen. ) 17 | 18 | ## [Troubleshooting](https://discuss.newrelic.com/t/troubleshooting-frameworks/108787) or [NR Diag](https://docs.newrelic.com/docs/using-new-relic/cross-product-functions/troubleshooting/new-relic-diagnostics) results 19 | [NOTE]: # ( Provide any other relevant log data. ) 20 | [TIP]: # ( Scrub logs and diagnostic information for sensitive information ) 21 | 22 | ## Steps to Reproduce 23 | [NOTE]: # ( Please be as specific as possible. ) 24 | [TIP]: # ( Link a sample application that demonstrates the issue. ) 25 | 26 | ## Your Environment 27 | [TIP]: # ( Include as many relevant details about your environment as possible including the running version of New Relic software and any relevant configurations. ) 28 | 29 | ## Additional context 30 | [TIP]: # ( Add any other context about the problem here. For example, relevant community posts or support tickets. ) 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Troubleshooting 4 | url: https://github.com/newrelic/open-by-default/blob/master/README.md#support 5 | about: Check out the README for troubleshooting directions 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | priority: '' 8 | --- 9 | 10 | ## Is your feature request related to a problem? Please describe. 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Feature Description 14 | A clear and concise description of the feature you want or need. 15 | 16 | ## Describe Alternatives 17 | A clear and concise description of any alternative solutions or features you've considered. Are there examples you could link us to? 18 | 19 | ## Additional context 20 | Add any other context here. 21 | 22 | ## Priority 23 | Please help us better understand this feature request by choosing a priority from the following options: 24 | [Nice to Have, Really Want, Must Have, Blocker] 25 | -------------------------------------------------------------------------------- /.github/workflows/push_pr.yml: -------------------------------------------------------------------------------- 1 | name: Lint and test code 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | e2e-test: 9 | name: E2E test 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | k8s-version: [ "v1.15.12", "v1.16.15", "v1.17.17", "v1.18.16", "v1.19.8", "v1.20.4" ] 15 | env: 16 | DOCKER_BUILDKIT: '1' # Setting DOCKER_BUILDKIT=1 ensures TARGETOS and TARGETARCH are populated 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: manusa/actions-setup-minikube@v2.3.0 20 | with: 21 | minikube version: v1.17.1 22 | kubernetes version: ${{ matrix.k8s-version }} 23 | driver: docker 24 | - name: Run e2e test 25 | continue-on-error: ${{ contains(matrix.k8s-version, '1.20') }} # experimental support for 1.20.x 26 | run: | 27 | # make minikube use local docker registry 28 | eval $(minikube -p minikube docker-env) 29 | 30 | # createa docker image and use it in minikube 31 | DOCKER_IMAGE_TAG=e2e-test make e2e-test 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: 5 | - prereleased 6 | - released 7 | 8 | jobs: 9 | publish: 10 | name: Release docker images 11 | runs-on: ubuntu-latest 12 | env: 13 | DOCKER_IMAGE_NAME: newrelic/k8s-webhook-cert-manager 14 | DOCKER_PLATFORMS: "linux/amd64,linux/arm64,linux/arm" 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Generate docker image version from git tag 19 | run: | 20 | echo "${{ github.event.release.tag_name }}" | grep -E '^v[0-9.]*[0-9]$' 21 | DOCKER_IMAGE_TAG=$(echo "${{ github.event.release.tag_name }}" | sed 's/^v//') 22 | echo "DOCKER_IMAGE_TAG=$DOCKER_IMAGE_TAG" >> $GITHUB_ENV 23 | 24 | - if: ${{ github.event.release.prerelease }} 25 | run: | 26 | echo "DOCKER_IMAGE_TAG=${DOCKER_IMAGE_TAG}-pre" >> $GITHUB_ENV 27 | 28 | - name: Set up QEMU 29 | uses: docker/setup-qemu-action@v1 30 | - name: Set up Docker Buildx 31 | uses: docker/setup-buildx-action@v1 32 | 33 | - uses: docker/login-action@v1 34 | with: 35 | username: ${{ secrets.FSI_DOCKERHUB_USERNAME }} 36 | password: ${{ secrets.FSI_DOCKERHUB_TOKEN }} 37 | 38 | - name: Build and push docker image 39 | run: | 40 | docker buildx build --push --platform=$DOCKER_PLATFORMS \ 41 | -t $DOCKER_IMAGE_NAME:$DOCKER_IMAGE_TAG \ 42 | . 43 | - name: Push :latest image 44 | if: ${{ ! github.event.release.prerelease }} 45 | run: | 46 | docker buildx build --push --platform=$DOCKER_PLATFORMS \ 47 | -t $DOCKER_IMAGE_NAME:latest \ 48 | . 49 | -------------------------------------------------------------------------------- /.github/workflows/repolinter.yml: -------------------------------------------------------------------------------- 1 | # NOTE: This file should always be named `repolinter.yml` to allow 2 | # workflow_dispatch to work properly 3 | name: Repolinter Action 4 | 5 | # NOTE: This workflow will ONLY check the default branch! 6 | # Currently there is no elegant way to specify the default 7 | # branch in the event filtering, so branches are instead 8 | # filtered in the "Test Default Branch" step. 9 | on: [push, workflow_dispatch] 10 | 11 | jobs: 12 | repolint: 13 | name: Run Repolinter 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Test Default Branch 17 | id: default-branch 18 | uses: actions/github-script@v2 19 | with: 20 | script: | 21 | const data = await github.repos.get(context.repo) 22 | return data.data && data.data.default_branch === context.ref.split('/').slice(-1)[0] 23 | - name: Checkout Self 24 | if: ${{ steps.default-branch.outputs.result == 'true' }} 25 | uses: actions/checkout@v2 26 | - name: Run Repolinter 27 | if: ${{ steps.default-branch.outputs.result == 'true' }} 28 | uses: newrelic/repolinter-action@v1 29 | with: 30 | config_url: https://raw.githubusercontent.com/newrelic/.github/main/repolinter-rulesets/community-plus.yml 31 | output_type: issue 32 | -------------------------------------------------------------------------------- /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](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## 1.4.0 8 | - Support multiarch images 9 | 10 | ## 1.3.0 11 | - Update to alpine 3.12.0 12 | 13 | ## 1.2.1 14 | - Revert to using the full service name for the CN. There is an open issue in 15 | EKS in which the SAN is not added to the signed certificates, making the 16 | TLS requests from the apiserver to the webhook fail. 17 | https://github.com/awslabs/amazon-eks-ami/issues/341 18 | - Validate that the length of the string "${service}.${namespace}.svc", which 19 | is used for the CN, is not greater than 64 characters as specified in the 20 | x509 spec. 21 | - Use ca bundle to patch the webhook from the service account secret instead 22 | of fetching via kubectl. 23 | - Set the number of retries for retrieving the issued certificate to 10 like 24 | the error message. 25 | - Add the `--webhook-kind` option to specified between 26 | MutatingWebhookConfiguration or ValidatingWebhookConfiguration. Defaults to 27 | MutatingWebhookConfiguration 28 | 29 | ## 1.2.0 30 | 31 | - Use a much shorter common name for the certificate (only the Service's name) 32 | to avoid problems due to the character limit in CNs. 33 | 34 | ## 1.1.1 35 | 36 | - Updated musl library to avoid security vulnerability: https://app.snyk.io/vuln/SNYK-LINUX-MUSL-458116 37 | 38 | ## 1.1.0 39 | 40 | ### Changed 41 | 42 | - Container user is now is `1000` instead of `root` 43 | 44 | ## 1.0.1 45 | 46 | ### Added 47 | 48 | - Better compatibility with Openshift by patching the webhook configuration json with an `add` operation instead of `replace`. 49 | 50 | ## 1.0.0 51 | - Initial version of the Kubernetes Webhook Certificate Manager. 52 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ### Hello! We're glad you've joined us. 2 | 3 | We believe participation in our community should be a harassment free experience for everyone. 4 | 5 | Learn more about our guidelines and principles by reading our [Code of Conduct](https://opensource.newrelic.com/code-of-conduct/) on our Open Source Website. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome. Before contributing please read the 4 | [code of conduct](./CODE_OF_CONDUCT.md) and [search the issue tracker](issues); your issue may have already been discussed or fixed in `main`. To contribute, 5 | [fork](https://help.github.com/articles/fork-a-repo/) this repository, commit your changes, and [send a Pull Request](https://help.github.com/articles/using-pull-requests/). 6 | 7 | Note that our [code of conduct](./CODE_OF_CONDUCT.md) applies to all platforms and venues related to this project; please follow it in all your interactions with the project and its participants. 8 | 9 | ## Feature Requests 10 | 11 | Feature requests should be submitted in the [Issue tracker](../../issues), with a description of the expected behavior & use case, where they’ll remain closed until sufficient interest, [e.g. :+1: reactions](https://help.github.com/articles/about-discussions-in-issues-and-pull-requests/), has been [shown by the community](../../issues?q=label%3A%22votes+needed%22+sort%3Areactions-%2B1-desc). 12 | Before submitting an Issue, please search for similar ones in the 13 | [closed issues](../../issues?q=is%3Aissue+is%3Aclosed+label%3Aenhancement). 14 | 15 | ## Pull Requests 16 | 17 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 18 | 2. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 19 | 3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. 20 | 21 | ## Contributor License Agreement 22 | 23 | Keep in mind that when you submit your Pull Request, you'll need to sign the CLA via the click-through using CLA-Assistant. If you'd like to execute our corporate CLA, or if you have any questions, please drop us an email at opensource@newrelic.com. 24 | 25 | For more information about CLAs, please check out Alex Russell’s excellent post, 26 | [“Why Do I Need to Sign This?”](https://infrequently.org/2008/06/why-do-i-need-to-sign-this/). 27 | 28 | ## Slack 29 | 30 | We host a public Slack with a dedicated channel for contributors and maintainers of open source projects hosted by New Relic. If you are contributing to this project, you're welcome to request access to the #oss-contributors channel in the newrelicusers.slack.com workspace. To request access, see https://newrelicusers-signup.herokuapp.com/. 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.13 2 | 3 | # This makes it easy to build tagged images with different `kubectl` versions. 4 | ARG KUBECTL_VERSION="v1.13.12" 5 | 6 | # Set by docker automatically 7 | ARG TARGETOS 8 | ARG TARGETARCH 9 | 10 | RUN apk --upgrade --no-cache add openssl && \ 11 | wget "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/${TARGETOS}/${TARGETARCH}/kubectl" && \ 12 | chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl 13 | 14 | WORKDIR /app 15 | USER 1000 16 | 17 | COPY --chmod=755 ./generate_certificate.sh /app/generate_certificate.sh 18 | 19 | CMD ["./generate_certificate.sh"] 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_IMAGE_NAME ?= newrelic/k8s-webhook-cert-manager 2 | DOCKER_IMAGE_TAG ?= latest 3 | KUBECTL_VERSION ?= v1.13.12 4 | 5 | 6 | .PHONY: all 7 | all: build 8 | 9 | .PHONY: build 10 | build: lint build-container 11 | 12 | .PHONY: build-container 13 | build-container: 14 | docker build -t $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) . 15 | 16 | .PHONY: build-container-e2e 17 | build-container-e2e: 18 | docker build --build-arg KUBECTL_VERSION=$(KUBECTL_VERSION) -t $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) . 19 | 20 | .PHONY: check-shellcheck 21 | check-shellcheck: 22 | @type shellcheck >/dev/null 2>&1 || (echo "You don't have shellcheck. Please install it: https://github.com/koalaman/shellcheck#installing" && exit 1) 23 | 24 | .PHONY: lint 25 | lint: check-shellcheck 26 | @echo "[lint] Validating code using shellcheck" 27 | @shellcheck generate_certificate.sh 28 | 29 | .PHONY: e2e-test 30 | e2e-test: 31 | @echo "[test] Running e2e tests" 32 | ./e2e-tests/tests.sh 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![New Relic Community Plus header](https://raw.githubusercontent.com/newrelic/open-source-office/master/examples/categories/images/Community_Plus.png)](https://opensource.newrelic.com/oss-category/#community-plus) 2 | 3 | # Kubernetes Webhook Certificate Manager 4 | 5 | [![Build Status](https://travis-ci.com/newrelic/k8s-webhook-cert-manager.svg?branch=master)](https://travis-ci.com/newrelic/k8s-webhook-cert-manager) 6 | 7 | Script to generate a certificate suitable for use with any Kubernetes Mutating or Validating Webhook. 8 | 9 | To be able to execute the script in a Kubernetes cluster, it's released as a Docker image and can be executed, for instance, as a Kubernetes Job. 10 | 11 | This is a detailed list of steps the script is executing: 12 | 13 | - Generate a server key. 14 | - If there is any previous CSR (certificate signing request) for this key, it is deleted. 15 | - Generate a CSR for such key. 16 | - The signature of the key is then approved. 17 | - The server's certificate is fetched from the CSR and then encoded. 18 | - A secret of type tls is created with the server certificate and key. 19 | - The k8s extension api server's CA bundle is fetched. 20 | - The mutating webhook configuration for the webhook server is patched with the k8s api server's CA bundle from the previous step. This CA bundle will be used by the k8s extension api server when calling our webhook. 21 | 22 | If you wish to learn more about TLS certificates management inside Kubernetes, check out the official documentation for [Managing TLS Certificate in a Cluster](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/#create-a-certificate-signing-request-object-to-send-to-the-kubernetes-api). 23 | 24 | ## Usage example 25 | 26 | The script expects multiple mandatory arguments. This is an example: 27 | 28 | ``` sh 29 | ./generate_certificate.sh --service ${WEBHOOK_SERVICE_NAME} --webhook 30 | ${WEBHOOK_NAME} --secret ${SECRET_NAME} --namespace ${WEBHOOK_NAMESPACE} 31 | ``` 32 | 33 | ## Development setup 34 | 35 | This script is designed to run in Kubernetes clusters. For development purposes, we recommend using [Minikube](https://github.com/kubernetes/minikube). 36 | 37 | ## Support 38 | 39 | Should you need assistance with New Relic products, you are in good hands with several support diagnostic tools and support channels. 40 | 41 | >This [troubleshooting framework](https://discuss.newrelic.com/t/troubleshooting-frameworks/108787) steps you through common troubleshooting questions. 42 | 43 | >New Relic offers NRDiag, [a client-side diagnostic utility](https://docs.newrelic.com/docs/using-new-relic/cross-product-functions/troubleshooting/new-relic-diagnostics) that automatically detects common problems with New Relic agents. If NRDiag detects a problem, it suggests troubleshooting steps. NRDiag can also automatically attach troubleshooting data to a New Relic Support ticket. Remove this section if it doesn't apply. 44 | 45 | If the issue has been confirmed as a bug or is a feature request, file a GitHub issue. 46 | 47 | **Support Channels** 48 | 49 | * [New Relic Documentation](https://docs.newrelic.com): Comprehensive guidance for using our platform 50 | * [New Relic Community](https://discuss.newrelic.com): The best place to engage in troubleshooting questions 51 | * [New Relic Developer](https://developer.newrelic.com/): Resources for building a custom observability applications 52 | * [New Relic University](https://learn.newrelic.com/): A range of online training for New Relic users of every level 53 | * [New Relic Technical Support](https://support.newrelic.com/) 24/7/365 ticketed support. Read more about our [Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/support-plan). 54 | 55 | ## Privacy 56 | 57 | At New Relic we take your privacy and the security of your information seriously, and are committed to protecting your information. We must emphasize the importance of not sharing personal data in public forums, and ask all users to scrub logs and diagnostic information for sensitive information, whether personal, proprietary, or otherwise. 58 | 59 | We define “Personal Data” as any information relating to an identified or identifiable individual, including, for example, your name, phone number, post code or zip code, Device ID, IP address, and email address. 60 | 61 | For more information, review [New Relic’s General Data Privacy Notice](https://newrelic.com/termsandconditions/privacy). 62 | 63 | ## Contribute 64 | 65 | We encourage your contributions to improve this project! Keep in mind that when you submit your pull request, you'll need to sign the CLA via the click-through using CLA-Assistant. You only have to sign the CLA one time per project. 66 | 67 | If you have any questions, or to execute our corporate CLA (which is required if your contribution is on behalf of a company), drop us an email at opensource@newrelic.com. 68 | 69 | **A note about vulnerabilities** 70 | 71 | As noted in our [security policy](/security/policy), New Relic is committed to the privacy and security of our customers and their data. We believe that providing coordinated disclosure by security researchers and engaging with the security community are important means to achieve our security goals. 72 | 73 | If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through [HackerOne](https://hackerone.com/newrelic). 74 | 75 | If you would like to contribute to this project, review [these guidelines](./CONTRIBUTING.md). 76 | 77 | To all contributors, we thank you! Without your contribution, this project would not be what it is today. 78 | 79 | ## License 80 | 81 | This project is licensed under the [Apache 2.0](http://apache.org/licenses/LICENSE-2.0.txt) License. 82 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | To release this project, go into Github [releases](https://github.com/arvdias/k8s-webhook-cert-manager/releases) and "Draft a new release". 4 | 5 | Use the next tag version targeting master and use the same value for the "Release title". 6 | 7 | In the description, add the same list of changes of the CHANGELOG.md. 8 | -------------------------------------------------------------------------------- /e2e-tests/k8s-e2e-bootstraping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # This file is a copy of https://github.com/newrelic/k8s-metadata-injection/blob/master/e2e-tests/k8s-e2e-bootstraping.sh 3 | 4 | E2E_KUBERNETES_VERSION=${E2E_E2E_KUBERNETES_VERSION:-} 5 | E2E_MINIKUBE_VERSION=${E2E_E2E_MINIKUBE_VERSION:-} 6 | E2E_SETUP_MINIKUBE=${E2E_SETUP_MINIKUBE:-} 7 | E2E_SETUP_KUBECTL=${E2E_SETUP_KUBECTL:-} 8 | E2E_START_MINIKUBE=${E2E_START_MINIKUBE:-} 9 | E2E_MINIKUBE_DRIVER=${E2E_MINIKUBE_DRIVER:-docker} 10 | E2E_SUDO=${E2E_SUDO:-} 11 | 12 | setup_minikube() { 13 | curl -sLo minikube https://storage.googleapis.com/minikube/releases/"$E2E_MINIKUBE_VERSION"/minikube-linux-amd64 \ 14 | && chmod +x minikube \ 15 | && $E2E_SUDO mv minikube /usr/local/bin/ 16 | 17 | export MINIKUBE_WANTREPORTERRORPROMPT=false 18 | export MINIKUBE_HOME=$HOME 19 | export CHANGE_MINIKUBE_NONE_USER=true 20 | } 21 | 22 | setup_kubectl() { 23 | curl -sLo kubectl https://storage.googleapis.com/kubernetes-release/release/"$E2E_KUBERNETES_VERSION"/bin/linux/amd64/kubectl \ 24 | && chmod +x kubectl \ 25 | && $E2E_SUDO mv kubectl /usr/local/bin/ 26 | 27 | mkdir "$HOME"/.kube || true 28 | touch "$HOME"/.kube/config 29 | export KUBECONFIG=$HOME/.kube/config 30 | } 31 | 32 | start_minikube() { 33 | minikube start --driver="$E2E_MINIKUBE_DRIVER" --kubernetes-version="$E2E_KUBERNETES_VERSION" --logtostderr 34 | } 35 | 36 | ### Bootstraping 37 | 38 | cd "$(dirname "$0")" 39 | 40 | [ -n "$E2E_SETUP_MINIKUBE" ] && setup_minikube 41 | 42 | minikube version 43 | 44 | [ -n "$E2E_SETUP_KUBECTL" ] && setup_kubectl 45 | 46 | kubectl version 47 | 48 | [ -n "$E2E_START_MINIKUBE" ] && start_minikube 49 | 50 | minikube update-context 51 | 52 | is_kube_running="false" 53 | 54 | set +e 55 | # this for loop waits until kubectl can access the api server that Minikube has created 56 | i=1 57 | while [ "$i" -ne 90 ] # timeout for 3 minutes 58 | do 59 | kubectl get po 1>/dev/null 2>&1 60 | if [ $? -ne 1 ]; then 61 | is_kube_running="true" 62 | break 63 | fi 64 | 65 | printf "waiting for Kubernetes cluster up\n" 66 | sleep 2 67 | i=$((i + 1)) 68 | done 69 | 70 | if [ $is_kube_running = "false" ]; then 71 | minikube logs 72 | printf "Kubernetes did not start within 3 minutes. Something went wrong.\n" 73 | exit 1 74 | fi 75 | set -e 76 | 77 | kubectl config view 78 | -------------------------------------------------------------------------------- /e2e-tests/manifests/cert-manager-job.yaml: -------------------------------------------------------------------------------- 1 | # https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/ 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: e2e-test-cert-manager-service-account 6 | namespace: default 7 | labels: 8 | app: e2e-test-cert-manager 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRole 12 | metadata: 13 | name: e2e-test-cert-manager-cluster-role 14 | namespace: default 15 | labels: 16 | app: e2e-test-cert-manager 17 | rules: 18 | - apiGroups: ["admissionregistration.k8s.io"] 19 | resources: ["mutatingwebhookconfigurations"] 20 | verbs: ["get", "create", "patch"] 21 | - apiGroups: ["certificates.k8s.io"] 22 | resources: ["certificatesigningrequests"] 23 | verbs: ["create", "get", "delete"] 24 | - apiGroups: ["certificates.k8s.io"] 25 | resources: 26 | - "certificatesigningrequests/status" 27 | - "certificatesigningrequests/approval" 28 | - "signers" 29 | verbs: ["update", "approve"] 30 | - apiGroups: [""] 31 | resources: ["secrets"] 32 | verbs: ["create", "get", "patch"] 33 | - apiGroups: [""] 34 | resources: ["configmaps"] 35 | verbs: ["get"] 36 | --- 37 | apiVersion: rbac.authorization.k8s.io/v1 38 | kind: ClusterRoleBinding 39 | metadata: 40 | name: e2e-test-cert-manager-role-binding 41 | namespace: default 42 | labels: 43 | app: e2e-test-cert-manager 44 | roleRef: 45 | apiGroup: rbac.authorization.k8s.io 46 | kind: ClusterRole 47 | name: e2e-test-cert-manager-cluster-role 48 | subjects: 49 | - kind: ServiceAccount 50 | name: e2e-test-cert-manager-service-account 51 | namespace: default 52 | --- 53 | apiVersion: batch/v1 54 | kind: Job 55 | metadata: 56 | name: e2e-test-cert-manager-setup 57 | namespace: default 58 | labels: 59 | app: e2e-test-cert-manager 60 | spec: 61 | template: 62 | metadata: 63 | namespace: default 64 | spec: 65 | serviceAccountName: e2e-test-cert-manager-service-account 66 | containers: 67 | - name: metadata-cert-setup 68 | # This is a minimal kubectl image based on Alpine Linux that signs certificates using the k8s extension api server 69 | # Use the commit hash so we always use the image we built 70 | image: newrelic/k8s-webhook-cert-manager:e2e-test 71 | command: ["./generate_certificate.sh"] 72 | args: 73 | - "--service" 74 | - "no-service" 75 | - "--webhook" 76 | - "e2e-test-cert-manager-webhook" 77 | - "--secret" 78 | - "e2e-test-cert-manager-secret" 79 | - "--namespace" 80 | - "default" 81 | restartPolicy: Never 82 | backoffLimit: 1 83 | -------------------------------------------------------------------------------- /e2e-tests/manifests/webhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1beta1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | name: e2e-test-cert-manager-webhook 5 | webhooks: 6 | - name: e2e-test-cert-manager-webhook.newrelic.com 7 | rules: 8 | - apiGroups: 9 | - "" 10 | apiVersions: 11 | - v1 12 | operations: 13 | - CREATE 14 | resources: 15 | - pods 16 | clientConfig: 17 | service: 18 | namespace: "default" 19 | name: "no-service" 20 | caBundle: "" 21 | -------------------------------------------------------------------------------- /e2e-tests/tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -e 3 | 4 | SECRET_NAME="e2e-test-cert-manager-secret" 5 | WEBHOOK_NAME="e2e-test-cert-manager-webhook" 6 | JOB_NAME="e2e-test-cert-manager-setup" 7 | 8 | # shellcheck disable=SC1090 9 | . "$(dirname "$0")/k8s-e2e-bootstraping.sh" 10 | 11 | get_pod_name_by_label() { 12 | pod_name="" 13 | i=1 14 | while [ "$i" -ne 10 ] 15 | do 16 | pod_name=$(kubectl get pods -l "$1" -o name | sed 's/pod\///g; s/pods\///g') 17 | if [ "$pod_name" != "" ]; then 18 | break 19 | fi 20 | sleep 1 21 | i=$((i + 1)) 22 | done 23 | printf "%s" "$pod_name" 24 | } 25 | 26 | wait_for_pod() { 27 | set +e 28 | desired_status=${2:-'Running'} 29 | is_pod_in_desired_status=false 30 | i=1 31 | while [ "$i" -ne 30 ] 32 | do 33 | pod_status="$(kubectl get pod "$1" -o jsonpath='{.status.phase}')" 34 | if [ "$pod_status" = "$desired_status" ]; then 35 | is_pod_in_desired_status=true 36 | printf "pod %s is %s\n" "$1" "$desired_status" 37 | break 38 | fi 39 | 40 | printf "Waiting for pod %s to be %s\n" "$1" "$desired_status" 41 | sleep 3 42 | i=$((i + 1)) 43 | done 44 | if [ $is_pod_in_desired_status = "false" ]; then 45 | printf "pod %s does not transition to %s within 1 minute 30 seconds\n" "$1" "$desired_status" 46 | kubectl get pods 47 | kubectl describe pod "$1" 48 | kubectl logs "$1" 49 | exit 1 50 | fi 51 | set -e 52 | } 53 | 54 | finish() { 55 | printf "calling cleanup function\n" 56 | kubectl delete -f manifests/ || true 57 | } 58 | 59 | # build container 60 | ( 61 | cd .. 62 | make build-container-e2e 63 | cd - 64 | ) 65 | 66 | trap finish EXIT 67 | 68 | kubectl create -f manifests/webhook.yaml 69 | # add imagePullPolicy=Never in the manifest. 70 | awk '/image: / { print; print " imagePullPolicy: Never"; next }1' manifests/cert-manager-job.yaml | kubectl create -f - 71 | 72 | label="job-name=${JOB_NAME}" 73 | job_pod_name=$(get_pod_name_by_label "$label") 74 | if [ "$job_pod_name" = '' ]; then 75 | printf 'pod with label %s not found\n' "$label" 76 | kubectl describe job $JOB_NAME 77 | exit 1 78 | fi 79 | wait_for_pod "$job_pod_name" "Succeeded" 80 | 81 | printf 'secret:\n' 82 | kubectl describe secret $SECRET_NAME 83 | printf '\n' 84 | 85 | printf 'mutating webhook configurations:\n' 86 | kubectl get mutatingwebhookconfiguration 87 | printf '\n' 88 | 89 | caBundle=$(kubectl get mutatingwebhookconfiguration $WEBHOOK_NAME -o jsonpath='{.webhooks[0].clientConfig.caBundle}') 90 | if [ "$caBundle" = '' ]; then 91 | printf "caBundle not found for %s\n" "$WEBHOOK_NAME" 92 | printf "webhook object:\n" 93 | kubectl describe mutatingwebhookconfiguration $WEBHOOK_NAME 94 | printf "job logs:\n" 95 | kubectl logs job/$JOB_NAME 96 | else 97 | printf "caBundle found in %s object\n" "$WEBHOOK_NAME" 98 | printf "Tests are passing successfully\n\n" 99 | fi 100 | -------------------------------------------------------------------------------- /generate_certificate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | # Fully qualified name of the CSR object 6 | csr="certificatesigningrequests.v1beta1.certificates.k8s.io" 7 | 8 | usage() { 9 | cat <> "${tmpdir}/csr.conf" 86 | [req] 87 | req_extensions = v3_req 88 | distinguished_name = req_distinguished_name 89 | [req_distinguished_name] 90 | [ v3_req ] 91 | basicConstraints = CA:FALSE 92 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 93 | extendedKeyUsage = serverAuth 94 | subjectAltName = @alt_names 95 | [alt_names] 96 | DNS.1 = ${service} 97 | DNS.2 = ${service}.${namespace} 98 | DNS.3 = ${fullServiceDomain} 99 | EOF 100 | 101 | openssl genrsa -out "${tmpdir}/server-key.pem" 2048 102 | openssl req -new -key "${tmpdir}/server-key.pem" -subj "/CN=${fullServiceDomain}" -out "${tmpdir}/server.csr" -config "${tmpdir}/csr.conf" 103 | 104 | csrName=${service}.${namespace} 105 | echo "creating csr: ${csrName} " 106 | set +e 107 | 108 | # clean-up any previously created CSR for our service. Ignore errors if not present. 109 | if kubectl get "${csr}/${csrName}"; then 110 | if kubectl delete "${csr}/${csrName}"; then 111 | echo "WARN: Previous CSR was found and removed." 112 | fi 113 | fi 114 | 115 | set -e 116 | 117 | # create server cert/key CSR and send it to k8s api 118 | cat <&2 162 | exit 1 163 | fi 164 | 165 | echo "${serverCert}" | openssl base64 -d -A -out "${tmpdir}/server-cert.pem" 166 | 167 | # create the secret with CA cert and server cert/key 168 | kubectl create secret tls "${secret}" \ 169 | --key="${tmpdir}/server-key.pem" \ 170 | --cert="${tmpdir}/server-cert.pem" \ 171 | --dry-run -o yaml | 172 | kubectl -n "${namespace}" apply -f - 173 | 174 | caBundle=$(base64 < /run/secrets/kubernetes.io/serviceaccount/ca.crt | tr -d '\n') 175 | 176 | set +e 177 | # Patch the webhook adding the caBundle. It uses an `add` operation to avoid errors in OpenShift because it doesn't set 178 | # a default value of empty string like Kubernetes. Instead, it doesn't create the caBundle key. 179 | # As the webhook is not created yet (the process should be done manually right after this job is created), 180 | # the job will not end until the webhook is patched. 181 | while true; do 182 | echo "INFO: Trying to patch webhook adding the caBundle." 183 | if kubectl patch "${kind:-mutatingwebhookconfiguration}" "${webhook}" --type='json' -p "[{'op': 'add', 'path': '/webhooks/0/clientConfig/caBundle', 'value':'${caBundle}'}]"; then 184 | break 185 | fi 186 | echo "INFO: webhook not patched. Retrying in 5s..." 187 | sleep 5 188 | done 189 | --------------------------------------------------------------------------------