From 0f91aaee4616113897abd65098f07781385b79c6 Mon Sep 17 00:00:00 2001 From: Anders Chen Date: Mon, 13 Feb 2023 17:55:54 -0500 Subject: [PATCH 001/264] Clarify zeroSSL setup instructions Update ZeroSSL setup instructions to clarify what values go where Signed-off-by: Anders Chen --- content/docs/tutorials/zerossl/zerossl.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/tutorials/zerossl/zerossl.md b/content/docs/tutorials/zerossl/zerossl.md index 7d0e5d2e59..2a5beae70c 100644 --- a/content/docs/tutorials/zerossl/zerossl.md +++ b/content/docs/tutorials/zerossl/zerossl.md @@ -63,14 +63,14 @@ Once you will get your credentials first step is to create seed with secrets. Th $ kubectl create secret generic \ zero-ssl-eabsecret \ --namespace=cert-manager \ - --from-literal=secret='YOUR_ZEROSSL_EAB_SECRET' + --from-literal=secret='YOUR_ZEROSSL_EAB_HMAC_KEY' ``` ### Another way of creating secret. Encode it in base64 first. ```bash -echo -n "YOUR_ZEROSSL_EAB_SECRET" | base64 -w 0 +echo -n "YOUR_ZEROSSL_EAB_HMAC_KEY" | base64 -w 0 ``` ```yaml @@ -79,7 +79,7 @@ kind: Secret metadata: name: zero-ssl-eabsecret data: - secret: YOUR_ENCODED_ZEROSSL_EAB_SECRET + secret: YOUR_ENCODED_ZEROSSL_EAB_HMAC_KEY ``` ```bash kubectl apply -f zero-ssl-eabsecret.yaml -n cert-manager @@ -105,7 +105,7 @@ spec: # for each cert-manager new EAB credencials are required externalAccountBinding: - keyID: ZEROSSL_KEY_ID + keyID: YOUR_ZEROSSL_EAB_KEY_ID keySecretRef: name: zero-ssl-eabsecret key: secret From c446cd820c86bebe42b2d347b1b38c4cd3f8c312 Mon Sep 17 00:00:00 2001 From: irbekrm Date: Wed, 22 Feb 2023 15:54:36 +0000 Subject: [PATCH 002/264] Updates uninstall doc with backup and restore guide link Signed-off-by: irbekrm --- content/docs/installation/uninstall.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/docs/installation/uninstall.md b/content/docs/installation/uninstall.md index abef2ffbd9..de06fbc9e7 100644 --- a/content/docs/installation/uninstall.md +++ b/content/docs/installation/uninstall.md @@ -9,4 +9,6 @@ two platforms is similar. Select the method that was used for installing cert-manager to go to the relevant uninstall documentation. - [kubectl](./kubectl.md#uninstalling) -- [helm](./helm.md#uninstalling) \ No newline at end of file +- [helm](./helm.md#uninstalling) + +If you need to preserve cert-manager custom resources (`Certificate`s, `Issuer`s etc), that are not version controlled or backed up by other means, take a look at our [backup and restore guide](../tutorials/backup.md). From b2711ba2e821c473fa9ff3f64bf75ae48bad7bef Mon Sep 17 00:00:00 2001 From: Kolja Date: Tue, 21 Feb 2023 08:26:33 -0800 Subject: [PATCH 003/264] Helm install: add hint for installCRDs in both cases Signed-off-by: Kolja Lubitz Helm install: add hint for installCRDs in both cases --- content/docs/installation/helm.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index f662f11f40..f9b1db130c 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -79,6 +79,7 @@ helm install \ --namespace cert-manager \ --create-namespace \ --version v1.11.0 \ + # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter ``` From f88241f42e1d91a11b9f84d0c1caf9af1d557424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 20 Feb 2023 19:08:13 +0100 Subject: [PATCH 004/264] release-process: explain how I fast-forwarded release-next MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index b772c59567..7e56c7b1f1 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -578,3 +578,32 @@ Other than the different `cert-manager/release` tag and `cmrel` version, you can is used for 1.6 and 1.7 - just remember to change the version of `cmrel` you install! [older-release-process]: https://github.com/cert-manager/website/blob/6fa0db74de0ae17d7be638a08155d1b4e036aaa9/content/en/docs/contributing/release-process.md?plain=1 + +## Documentation Process + +Everytime a PR is opened on the cert-manager/website repository, you will need +to check that the "base branch" is correct. + +| Description | Base branch | Folder | +|--------------------------------------------|----------------|----------------| +| Documentation PR for an existing feature | `master` | `content/docs` | +| Documentation PR for an unreleased feature | `release-next` | `content/docs` | + +When creating a documentation PR for an unreleased feature, you will need to +fast-forward the `release-next` branch to `master` before asking for a review of +the PR: + +1. Create a PR to fast-forward `release-next` to `master` using [this magic + link][ff-release-next]. +2. If you see the label `dco-signoff: no`, add a comment on the PR with: + + ```text + /override dco + ``` + + It is necessary because some the merge commits have been written by the bot + and do not have a DCO signoff. +3. Once the PR is merged, look at your PR and check that the unrelated commits + that were previously showing are gone. + +[ff-release-next]: https://github.com/cert-manager/website/compare/release-next...master?quick_pull=1&title=%5Brelease-next%5D+Fast-forward+to+master&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco \ No newline at end of file From c8c36a905633b297645659f5612e031eabb7934c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 27 Feb 2023 16:37:34 +0100 Subject: [PATCH 005/264] release-process: address Richard's comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 7e56c7b1f1..164516778f 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -581,19 +581,21 @@ is used for 1.6 and 1.7 - just remember to change the version of `cmrel` you ins ## Documentation Process -Everytime a PR is opened on the cert-manager/website repository, you will need -to check that the "base branch" is correct. +Authors of PRs to cert-manager/website need to check that the "base branch" is +correct: | Description | Base branch | Folder | |--------------------------------------------|----------------|----------------| | Documentation PR for an existing feature | `master` | `content/docs` | | Documentation PR for an unreleased feature | `release-next` | `content/docs` | -When creating a documentation PR for an unreleased feature, you will need to -fast-forward the `release-next` branch to `master` before asking for a review of -the PR: +In rare occasions, when writing documentation for an unreleased feature, you may +notice that some recent changes in `master` aren't present in `release-next`. If +that is a problem, you will want to update `release-next` branch with the latest +changes from `master`. To update `release-next` with the changes made to +`master`, follow these steps: -1. Create a PR to fast-forward `release-next` to `master` using [this magic +1. Create a PR to merge `master` into `release-next` using [this magic link][ff-release-next]. 2. If you see the label `dco-signoff: no`, add a comment on the PR with: @@ -603,7 +605,5 @@ the PR: It is necessary because some the merge commits have been written by the bot and do not have a DCO signoff. -3. Once the PR is merged, look at your PR and check that the unrelated commits - that were previously showing are gone. -[ff-release-next]: https://github.com/cert-manager/website/compare/release-next...master?quick_pull=1&title=%5Brelease-next%5D+Fast-forward+to+master&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco \ No newline at end of file +[ff-release-next]: https://github.com/cert-manager/website/compare/release-next...master?quick_pull=1&title=%5Brelease-next%5D+Fast-forward+to+master&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco From 06cf8a87c7226f56710b0ecb7ca53f14ff8975d3 Mon Sep 17 00:00:00 2001 From: irbekrm Date: Thu, 2 Mar 2023 11:31:52 +0000 Subject: [PATCH 006/264] Explains what happens if CertificateRequest is denied Signed-off-by: irbekrm --- content/docs/projects/approver-policy.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/content/docs/projects/approver-policy.md b/content/docs/projects/approver-policy.md index 4b991df08f..9c105ae67e 100644 --- a/content/docs/projects/approver-policy.md +++ b/content/docs/projects/approver-policy.md @@ -72,6 +72,14 @@ selector. least one policy is appropriate for the request but none of those permit the request, the request is denied.** +A denied CertificateRequest is considered to be permanently failed. If it was +created for a Certificate resource, the issuance will be retried with +[exponential +backoff](../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) +like all other permanent issuance failures. A CertificateRequest that is neither +approved nor denied (because no matching policy was found) will not be further +processed by cert-manager until it gets either approved or denied. + CertificateRequestPolicies are cluster scoped resources that can be thought of as "policy profiles". They describe any request that is approved by that policy. Policies are bound to Kubernetes users and ServiceAccounts using RBAC. From edfee1b1d5689d1ec7ee70d953b1f8cfd8a4b686 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 22 Mar 2023 10:11:56 +0000 Subject: [PATCH 007/264] Remove unused algolia indexing script Signed-off-by: Richard Wall --- scripts/index | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100755 scripts/index diff --git a/scripts/index b/scripts/index deleted file mode 100755 index 7f3c140990..0000000000 --- a/scripts/index +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2022 The cert-manager Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail - -# TODO: Re-implement indexing for the new website - -REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}" - -if [ -z ${ALGOLIA_WRITE_KEY+x} ]; then - echo "Missing ALGOLIA_WRITE_KEY, skipping index" - exit -fi - -cd "${REPO_ROOT}" - -"${REPO_ROOT}/scripts/update-node_modules" - -# TODO: this is commented out because it won't work on the new website, and because the variables we need here aren't documented (we don't know what our ALGOLIA_WRITE_KEY is) - -#${REPO_ROOT}/node_modules/.bin/hugo-algolia -i 'content/docs/**' --config ./config/_default/config.toml --config-toml --all -s -## A small hack to enable snippets: -#TMPFILE=$(mktemp) -#${REPO_ROOT}/node_modules/.bin/algolia getsettings -a 18N9PEKHUC -k $ALGOLIA_WRITE_KEY -n cert-manager-latest > $TMPFILE -#${REPO_ROOT}/node_modules/.bin/algolia setsettings -a 18N9PEKHUC -k $ALGOLIA_WRITE_KEY -n cert-manager-latest -p '{"attributesToSnippet": ["content:6"]}' -s $TMPFILE -# -## Do versioning -#for i in content/*-docs/; do -# ${REPO_ROOT}/node_modules/.bin/hugo-algolia -i "$i**" --config ./config/_default/config.toml --config-toml --all -s --override-index-name cert-manager-$(echo $i | grep -Po "v.*(?=-docs)") -# # A small hack to enable snippets: -# TMPFILE=$(mktemp) -# ${REPO_ROOT}/node_modules/.bin/algolia getsettings -a 18N9PEKHUC -k $ALGOLIA_WRITE_KEY -n cert-manager-$(echo $i | grep -Po "v.*(?=-docs)") > $TMPFILE -# ${REPO_ROOT}/node_modules/.bin/algolia setsettings -a 18N9PEKHUC -k $ALGOLIA_WRITE_KEY -n cert-manager-$(echo $i | grep -Po "v.*(?=-docs)") -p '{"attributesToSnippet": ["content:6"]}' -s $TMPFILE -#done From e68f1c65a6f2ab48f62505e4dab2ec927d8bd5bb Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 22 Mar 2023 10:12:20 +0000 Subject: [PATCH 008/264] Remove calls to deleted `update-node_modules` script Signed-off-by: Richard Wall --- scripts/gendocs/generate-new-import-path-docs | 2 -- scripts/gendocs/generate-old-import-path-docs | 2 -- 2 files changed, 4 deletions(-) diff --git a/scripts/gendocs/generate-new-import-path-docs b/scripts/gendocs/generate-new-import-path-docs index d7b19b0531..9f7f9ac128 100755 --- a/scripts/gendocs/generate-new-import-path-docs +++ b/scripts/gendocs/generate-new-import-path-docs @@ -48,8 +48,6 @@ cleanup() { } trap cleanup EXIT -"${REPO_ROOT}/scripts/update-node_modules" - # Create fake GOPATH echo "+++ Creating temporary GOPATH" export GOPATH="${tmpdir}/go" diff --git a/scripts/gendocs/generate-old-import-path-docs b/scripts/gendocs/generate-old-import-path-docs index e68cc4dad8..6ea1765ea4 100755 --- a/scripts/gendocs/generate-old-import-path-docs +++ b/scripts/gendocs/generate-old-import-path-docs @@ -48,8 +48,6 @@ cleanup() { } trap cleanup EXIT -"${REPO_ROOT}/scripts/update-node_modules" - # Create fake GOPATH echo "+++ Creating temporary GOPATH" export GOPATH="${tmpdir}/go" From 4a71e831b042e4e1781247dbb62cca345766146d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Wed, 22 Mar 2023 11:30:06 +0100 Subject: [PATCH 009/264] gendocs: use GOMODCACHE and use a shallow clone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we are now using GOMODCACHE, we don't need to first run go clean -modcache to work around the read-only permission errors. Also, there is no need to "git fetch" when we just cloned. I also fixed a few shellcheck warnings. Signed-off-by: Maël Valais --- scripts/gendocs/generate-new-import-path-docs | 77 ++++++------ scripts/gendocs/generate-old-import-path-docs | 111 ++++++++---------- 2 files changed, 85 insertions(+), 103 deletions(-) diff --git a/scripts/gendocs/generate-new-import-path-docs b/scripts/gendocs/generate-new-import-path-docs index 9f7f9ac128..b2289b119c 100755 --- a/scripts/gendocs/generate-new-import-path-docs +++ b/scripts/gendocs/generate-new-import-path-docs @@ -24,30 +24,28 @@ set -o pipefail REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}" if ! command -v go &>/dev/null; then - echo "Ensure go is installed" - exit 1 + echo "Ensure go is installed" + exit 1 fi if ! command -v npm &>/dev/null; then - echo "Ensure npm is installed" - exit 1 + echo "Ensure npm is installed" + exit 1 fi tmpdir="$(mktemp -d)" apidocstmpdir="$(mktemp -d)" cleanup() { - # we can't simply remove tmpdir because the modcache is written as read-only - # and we'll get permissions errors, so we use go clean instead - export GO111MODULE="auto" - echo "+++ Cleaning up temporary GOPATH" - go clean -modcache - - rm -rf "${apidocstmpdir}" - rm -rf "${tmpdir}" + echo "+++ Cleaning up temporary GOPATH" + rm -rf "${apidocstmpdir}" + rm -rf "${tmpdir}" } trap cleanup EXIT +GOMODCACHE="$(go env GOMODCACHE)" +export GOMODCACHE + # Create fake GOPATH echo "+++ Creating temporary GOPATH" export GOPATH="${tmpdir}/go" @@ -59,18 +57,12 @@ export GOBIN go install github.com/ahmetb/gen-crd-api-reference-docs@v0.3.0 -mkdir -p "${GOPATH}/src/github.com/cert-manager" -gitdir="${GOPATH}/src/github.com/cert-manager/cert-manager" -echo "+++ Cloning cert-manager repository..." -git clone "https://github.com/cert-manager/cert-manager.git" "$gitdir" -cd "$gitdir" - # genversion takes two arguments (branch in cert-manager repo and a directory in # this repo under content) and generates API reference docs from cert-manager # branch for the path in this repo. genversion() { - checkout "$1" - gendocs "$2" + checkout "$1" + gendocs "$2" } genversionwithcli() { @@ -89,30 +81,31 @@ genversionwithcli() { } checkout() { - branch="$1" - pushd "$gitdir" - rm -rf vendor/ - echo "+++ Checking out branch $branch" - git fetch origin "$branch" - git reset --hard "origin/$branch" - echo "+++ Running 'go mod vendor' (this may take a while)" - go mod vendor + branch="$1" + + mkdir -p "${GOPATH}/src/github.com/cert-manager" + gitdir="${GOPATH}/src/github.com/cert-manager/cert-manager" + echo "+++ Cloning cert-manager repository..." + git clone "https://github.com/cert-manager/cert-manager.git" "$gitdir" --depth 1 --branch="$branch" + pushd "$gitdir" + echo "+++ Running 'go mod vendor'" + go mod vendor } gendocs() { - outputdir="$1" - mkdir -p ${apidocstmpdir}/${outputdir}/ - echo "+++ Generating reference docs..." - "${GOBIN}/gen-crd-api-reference-docs" \ - -config "${REPO_ROOT}/scripts/gendocs/config.json" \ - -template-dir "${REPO_ROOT}/scripts/gendocs/templates" \ - -api-dir "./pkg/apis" \ - -out-file ${apidocstmpdir}/${outputdir}/api-docs.md - - ${REPO_ROOT}/scripts/gendocs/postprocess/api-doc-postprocess.js <${apidocstmpdir}/${outputdir}/api-docs.md > "${REPO_ROOT}/content/${outputdir}/reference/api-docs.md" - - rm -rf vendor/ - popd + outputdir="$1" + mkdir -p "${apidocstmpdir}/${outputdir}/" + echo "+++ Generating reference docs..." + "${GOBIN}/gen-crd-api-reference-docs" \ + -config "${REPO_ROOT}/scripts/gendocs/config.json" \ + -template-dir "${REPO_ROOT}/scripts/gendocs/templates" \ + -api-dir "./pkg/apis" \ + -out-file "${apidocstmpdir}/${outputdir}/api-docs.md" + + "${REPO_ROOT}"/scripts/gendocs/postprocess/api-doc-postprocess.js <"${apidocstmpdir}/${outputdir}/api-docs.md" >"${REPO_ROOT}/content/${outputdir}/reference/api-docs.md" + + rm -rf vendor/ + popd } # genclireference will attempt to run main.go --help for the target and write the output to a markdown file @@ -137,7 +130,7 @@ genclireference() { mkdir -p "${REPO_ROOT}/content/${outputdir}/cli/" output=$(go run "$target/main.go" --help) - cat > "${REPO_ROOT}/content/${outputdir}/cli/$name.md" << EOF + cat >"${REPO_ROOT}/content/${outputdir}/cli/$name.md" </dev/null; then - echo "Ensure go is installed" - exit 1 + echo "Ensure go is installed" + exit 1 fi if ! command -v npm &>/dev/null; then - echo "Ensure npm is installed" - exit 1 + echo "Ensure npm is installed" + exit 1 fi tmpdir="$(mktemp -d)" apidocstmpdir="$(mktemp -d)" cleanup() { - # we can't simply remove tmpdir because the modcache is written as read-only - # and we'll get permissions errors, so we use go clean instead - export GO111MODULE="auto" - echo "+++ Cleaning up temporary GOPATH" - go clean -modcache - - rm -rf "${apidocstmpdir}" - rm -rf "${tmpdir}" + rm -rf "${apidocstmpdir}" + rm -rf "${tmpdir}" } trap cleanup EXIT @@ -59,18 +53,12 @@ export GOBIN go install github.com/ahmetb/gen-crd-api-reference-docs@v0.3.0 -mkdir -p "${GOPATH}/src/github.com/jetstack" -gitdir="${GOPATH}/src/github.com/jetstack/cert-manager" -echo "+++ Cloning cert-manager repository..." -git clone "https://github.com/jetstack/cert-manager.git" "$gitdir" -cd "$gitdir" - # genversion takes two arguments (branch in cert-manager repo and a directory in # this repo under content) and generates API reference docs from cert-manager # branch for the path in this repo. genversion() { - checkout "$1" - gendocs "$2" + checkout "$1" + gendocs "$2" } genversionwithcli() { @@ -89,30 +77,31 @@ genversionwithcli() { } checkout() { - branch="$1" - pushd "$gitdir" - rm -rf vendor/ - echo "+++ Checking out branch $branch" - git fetch origin "$branch" - git reset --hard "origin/$branch" - echo "+++ Running 'go mod vendor' (this may take a while)" - go mod vendor + branch="$1" + + mkdir -p "${GOPATH}/src/github.com/jetstack" + gitdir="${GOPATH}/src/github.com/jetstack/cert-manager" + echo "+++ Cloning cert-manager repository..." + git clone "https://github.com/jetstack/cert-manager.git" "$gitdir" --depth 1 --branch "$branch" + pushd "$gitdir" + echo "+++ Running 'go mod vendor' (this may take a while)" + go mod vendor } gendocs() { - outputdir="$1" - mkdir -p ${apidocstmpdir}/${outputdir}/ - echo "+++ Generating reference docs..." - "${GOBIN}/gen-crd-api-reference-docs" \ - -config "${REPO_ROOT}/scripts/gendocs/config.json" \ - -template-dir "${REPO_ROOT}/scripts/gendocs/templates" \ - -api-dir "./pkg/apis" \ - -out-file ${apidocstmpdir}/${outputdir}/api-docs.md - - ${REPO_ROOT}/scripts/gendocs/postprocess/api-doc-postprocess.js <${apidocstmpdir}/${outputdir}/api-docs.md > "${REPO_ROOT}/content/${outputdir}/reference/api-docs.md" - - rm -rf vendor/ - popd + outputdir="$1" + mkdir -p "${apidocstmpdir}/${outputdir}"/ + echo "+++ Generating reference docs..." + "${GOBIN}/gen-crd-api-reference-docs" \ + -config "${REPO_ROOT}/scripts/gendocs/config.json" \ + -template-dir "${REPO_ROOT}/scripts/gendocs/templates" \ + -api-dir "./pkg/apis" \ + -out-file "${apidocstmpdir}/${outputdir}"/api-docs.md + + "${REPO_ROOT}"/scripts/gendocs/postprocess/api-doc-postprocess.js <"${apidocstmpdir}/${outputdir}/api-docs.md" >"${REPO_ROOT}/content/${outputdir}/reference/api-docs.md" + + rm -rf vendor/ + popd } # genclireference will attempt to run main.go --help for the target and write the output to a markdown file @@ -143,7 +132,7 @@ genclireference() { mkdir -p "${REPO_ROOT}/content/${outputdir}/cli/" output=$(go run "$target/main.go" --help) - cat > "${REPO_ROOT}/content/${outputdir}/cli/$name.md" << EOF + cat >"${REPO_ROOT}/content/${outputdir}/cli/$name.md" < Date: Wed, 22 Mar 2023 19:09:33 +0000 Subject: [PATCH 010/264] Clarify ingress-shim annotations Signed-off-by: irbekrm --- content/docs/usage/ingress.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index d8f58e002e..e70c77390f 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -49,13 +49,25 @@ spec: You can specify the following annotations on Ingress resources in order to trigger Certificate resources to be automatically created: -- `cert-manager.io/issuer`: the name of an Issuer to acquire the certificate - required for this Ingress. The Issuer *must* be in the same namespace as the - Ingress resource. +- `cert-manager.io/issuer`: the name of the issuer that should issue the certificate + required for this Ingress. -- `cert-manager.io/cluster-issuer`: the name of a ClusterIssuer to acquire the - certificate required for this Ingress. It does not matter which namespace your - Ingress resides, as ClusterIssuers are non-namespaced resources. + > ⚠️ This annotation does _not_ assume a namespace scoped issuer. It will + default to cert-manager.io Issuer, however in case of external issuer types, + this should be used for both namespaced and cluster scoped issuer types. + + > ⚠️ If a namespace scoped issuer is used then the issuer *must* be in + the same namespace as the Ingress resource. + +- `cert-manager.io/cluster-issuer`: the name of a cert-manager.io ClusterIssuer + to acquire the certificate required for this Ingress. It does not matter which + namespace your Ingress resides, as ClusterIssuers are non-namespaced + resources. + + > ⚠️ This annotation is a shortcut to refer to to + cert-manager.io ClusterIssuer without having to specify group and kind. It is + _not_ intended to be used to specify an external cluster-scoped issuer- please + use `cert-manager.io/issuer` annotation for those. - `cert-manager.io/issuer-kind`: the kind of the external issuer resource, for example `AWSPCAIssuer`. This is only necessary for out-of-tree issuers. From b35f704a00c56dec2f512f73fa09ac5ec645b110 Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Sun, 26 Mar 2023 21:15:00 +0300 Subject: [PATCH 011/264] Update references to ingress objects in the nginx-ingress tutorial From `ingress.extensions` to `ingress.networking.k8s.io`. The objects referenced in the page are not of type `ingress.extensions` Signed-off-by: Yarden Shoham --- content/docs/tutorials/acme/nginx-ingress.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/tutorials/acme/nginx-ingress.md b/content/docs/tutorials/acme/nginx-ingress.md index 6b98f348e3..9a334f4810 100644 --- a/content/docs/tutorials/acme/nginx-ingress.md +++ b/content/docs/tutorials/acme/nginx-ingress.md @@ -136,7 +136,7 @@ it is saved: ```bash kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/ingress.yaml -# expected output: ingress.extensions "kuard" created +# expected output: ingress.networking.k8s.io/kuard created ``` > Note: The ingress example we show above has a `host` definition within it. The @@ -348,7 +348,7 @@ and apply it: ```bash kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/ingress-tls.yaml -# expected output: ingress.extensions "kuard" configured +# expected output: ingress.networking.k8s.io/kuard configured ``` Cert-manager will read these annotations and use them to create a certificate, @@ -447,7 +447,7 @@ can update the annotations in the ingress to specify the production issuer: ```bash $ kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/ingress-tls-final.yaml -ingress.extensions "kuard" configured +ingress.networking.k8s.io/kuard configured ``` You will also need to delete the existing secret, which cert-manager is watching From b97cd3af99e57ff5edf228455e7e58e71c25a065 Mon Sep 17 00:00:00 2001 From: Anders Chen Date: Mon, 27 Mar 2023 10:01:10 -0400 Subject: [PATCH 012/264] Remove outdated warning Signed-off-by: Anders Chen --- content/docs/tutorials/zerossl/zerossl.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/content/docs/tutorials/zerossl/zerossl.md b/content/docs/tutorials/zerossl/zerossl.md index 2a5beae70c..e10d997ec9 100644 --- a/content/docs/tutorials/zerossl/zerossl.md +++ b/content/docs/tutorials/zerossl/zerossl.md @@ -13,9 +13,6 @@ The ZeroSSL just like Let's Encrypt and its competitors allows to create free 90 `Please note!` \ EAB credentials are not stored in your account, please make sure to note them somewhere. Each click on "Generate" will create a new set of credentials. Even if you create multiple credentials, all of them will remain functional. -`Please note!` \ -EAB credentials are one-use only. Create additional pair for different environments. - # Prerequisites From 5a414e979cee6c6641f4016de513150eac69bcd1 Mon Sep 17 00:00:00 2001 From: sdarwin Date: Mon, 27 Mar 2023 14:52:23 -0600 Subject: [PATCH 013/264] Installation note about gke autopilot Signed-off-by: sdarwin --- content/docs/installation/compatibility.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/docs/installation/compatibility.md b/content/docs/installation/compatibility.md index efb53c9aad..72bd3fbf31 100644 --- a/content/docs/installation/compatibility.md +++ b/content/docs/installation/compatibility.md @@ -84,6 +84,8 @@ helm install \ --version ${CERT_MANAGER_VERSION} --set global.leaderElection.namespace=cert-manager ``` +The implication for a `kubectl apply` type installation is then either "you must manually update the manifests prior to installation by replacing kube-system with cert-manager" or "Don't install cert-manager using kubectl apply. Helm is the recommended solution". + ## AWS EKS When using a custom CNI (such as Weave or Calico) on EKS, the webhook cannot be @@ -109,4 +111,4 @@ the webhook listens. See the warning at the top of this page for more details. Because Fargate forces you to use its networking, you cannot manually set the networking type and options such as `webhook.hostNetwork` on the helm chart will cause your -cert-manager deployment to fail in surprising ways. \ No newline at end of file +cert-manager deployment to fail in surprising ways. From eb918cc1bfb2331cd97af3a82821a6b0ad33177f Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Tue, 28 Mar 2023 16:02:55 +0100 Subject: [PATCH 014/264] fix unrelated spelling issue which broke CI Signed-off-by: Ashley Davis --- content/docs/installation/compatibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/compatibility.md b/content/docs/installation/compatibility.md index 72bd3fbf31..516bc941a0 100644 --- a/content/docs/installation/compatibility.md +++ b/content/docs/installation/compatibility.md @@ -84,7 +84,7 @@ helm install \ --version ${CERT_MANAGER_VERSION} --set global.leaderElection.namespace=cert-manager ``` -The implication for a `kubectl apply` type installation is then either "you must manually update the manifests prior to installation by replacing kube-system with cert-manager" or "Don't install cert-manager using kubectl apply. Helm is the recommended solution". +The implication for a `kubectl apply` type installation is then either "you must manually update the manifests prior to installation by replacing `kube-system` with cert-manager" or "Don't install cert-manager using kubectl apply. Helm is the recommended solution". ## AWS EKS From a8db0d6731de23283785cae4145c0f51f79dc617 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Tue, 28 Mar 2023 15:58:28 +0100 Subject: [PATCH 015/264] fix typos / some wording for approver-policy docs Signed-off-by: Ashley Davis --- content/docs/projects/approver-policy.md | 30 ++++++++++-------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/content/docs/projects/approver-policy.md b/content/docs/projects/approver-policy.md index 9c105ae67e..935f46cce9 100644 --- a/content/docs/projects/approver-policy.md +++ b/content/docs/projects/approver-policy.md @@ -1,27 +1,26 @@ --- title: approver-policy -description: '' +description: 'Policy plugin for cert-manager' --- approver-policy is a cert-manager [approver](../concepts/certificaterequest.md#approval) -that will approve or deny CertificateRequests based on CRD defined policies. - ---- +that will approve or deny CertificateRequests based on policies defined in +the `CertificateRequestPolicy` custom resource. ## Installation -[cert-manager](../installation/README.md) is required to be installed with approver-policy. +[cert-manager](../installation/README.md) is a dependency of approver-policy. > ⚠️ > > It is important that the > [default approver is disabled in cert-manager](../concepts/certificaterequest.md#approver-controller). > If the default approver is not disabled in cert-manager, approver-policy will -> race with cert-manager and thus policy becomes useless. +> race with cert-manager and policy will be ineffective. > > ```terminal -> $ helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set extraArgs={--controllers='*\,-certificaterequests-approver'} --set installCRDs=true --create-namespace +> helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set extraArgs={--controllers='*\,-certificaterequests-approver'} --set installCRDs=true --create-namespace > ``` > > ⚠️ @@ -29,8 +28,8 @@ that will approve or deny CertificateRequests based on CRD defined policies. To install approver-policy: ```terminal -$ helm repo add jetstack https://charts.jetstack.io --force-update -$ helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait +helm repo add jetstack https://charts.jetstack.io --force-update +helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait ``` If you are using approver-policy with [external @@ -39,7 +38,7 @@ include their signer names so that approver-policy has permissions to approve and deny CertificateRequests that [reference them](../concepts/certificaterequest.md#rbac-syntax). For example, if using approver-policy for the internal issuer types, along with -[google-ca-issuer](https://github.com/jetstack/google-cas-issuer), and +[google-cas-issuer](https://github.com/jetstack/google-cas-issuer), and [aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer), set the following values when installing: @@ -52,8 +51,6 @@ awspcaclusterissuers.awspca.cert-manager.io/*,awspcaissuers.awspca.cert-manager. }" ``` ---- - ## Configuration > Example policy resources can be found @@ -74,8 +71,7 @@ request, the request is denied.** A denied CertificateRequest is considered to be permanently failed. If it was created for a Certificate resource, the issuance will be retried with -[exponential -backoff](../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) +[exponential backoff](../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) like all other permanent issuance failures. A CertificateRequest that is neither approved nor denied (because no matching policy was found) will not be further processed by cert-manager until it gets either approved or denied. @@ -132,8 +128,6 @@ subjects: apiGroup: rbac.authorization.k8s.io ``` ---- - ## Behavior CertificateRequestPolicy are split into 4 parts; `allowed`, `contraints`, @@ -373,7 +367,7 @@ time. Plugins are designed to be used as extensions to the existing policy checks where the user requires special functionality that the existing checks can't provide. -Plugins are defined as a block on the CertificateRequestPolicy Spec. +Plugins are defined as a block on the CertificateRequestPolicy `spec`. ```yaml apiVersion: policy.cert-manager.io/v1alpha1 @@ -388,4 +382,4 @@ spec: val-1: key-1 ``` -There are currently no none open source plugins. +There are currently no open source plugins. From 9f09b25c718ddaa661563687141d3d4954a0c11c Mon Sep 17 00:00:00 2001 From: sdarwin Date: Wed, 29 Mar 2023 19:11:34 -0600 Subject: [PATCH 016/264] Updated info about issue-temporary-certificate Signed-off-by: sdarwin --- content/docs/usage/certificate.md | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index d0bce92755..7586f9cb95 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -136,24 +136,24 @@ documentation](../reference/api-docs.md#cert-manager.io/v1.KeyUsage).

Temporary Certificates while Issuing

-On old GKE versions (`1.10.7-gke.1` and below), when requesting certificates -[using the ingress-shim](./ingress.md) alongside the -[`ingress-gce`](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress) -ingress controller, `ingress-gce` -[required](https://github.com/kubernetes/ingress-gce/pull/388) a temporary -certificate must be present while waiting for the issuance of a signed -certificate. Note that this issue was -[solved](https://github.com/cert-manager/cert-manager/issues/606#issuecomment-424397233) -in `1.10.7-gke.2`. +When requesting certificates [using the ingress-shim](./ingress.md), the +component `ingress-gce`, if used, requires that a temporary certificate is +present while waiting for the issuance of a signed certificate when serving. To +facilitate this, if the following annotation: -```yaml -# Required for GKE 1.10.7-gke.1 and below. -cert-manager.io/issue-temporary-certificate": "true" -``` + ```yaml + cert-manager.io/issue-temporary-certificate": "true" + ``` + +is present on the certificate, a self-signed temporary certificate will be +present on the `Secret` until it is overwritten once the signed certificate has +been issued. + +Adding the following annotation on an ingress will automatically set "issue-temporary-certificate" on the certificate: -That made sure that a temporary self-signed certificate was present in the -`Secret`. The self-signed certificate was replaced with the signed certificate -later on. + ```yaml + acme.cert-manager.io/http01-edit-in-place: "true" + ```

Rotation of the private key

From 3d954f2eaf204ee3b919e53a18fd7d03c521f040 Mon Sep 17 00:00:00 2001 From: irbekrm Date: Fri, 3 Mar 2023 06:50:32 +0000 Subject: [PATCH 017/264] Fix CertificateRequest and failed issuance info To correctly record what happens with denied CRs and how issuance gets retried Signed-off-by: irbekrm --- content/docs/concepts/certificaterequest.md | 27 +++++++++++++-- content/docs/contributing/external-issuers.md | 9 +---- content/docs/faq/README.md | 34 +++++++++++++++---- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/content/docs/concepts/certificaterequest.md b/content/docs/concepts/certificaterequest.md index e8f102d4ef..5dbc1ca546 100644 --- a/content/docs/concepts/certificaterequest.md +++ b/content/docs/concepts/certificaterequest.md @@ -60,7 +60,6 @@ manage the logic and life cycle of `CertificateRequests`. used and relied upon by controllers or services to make decisions on what actions to take next on the resource. - ### Ready Each ready condition consists of the pair `Ready` - a boolean value, and `Reason` - a string. The set of values and meanings are as follows: @@ -68,8 +67,30 @@ Each ready condition consists of the pair `Ready` - a boolean value, and | Ready | Reason | Condition Meaning | | ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | False | Pending | The `CertificateRequest` is currently pending, waiting for some other operation to take place. This could be that the `Issuer` does not exist yet or the `Issuer` is in the process of issuing a certificate. | -| False | Failed | The certificate has failed to be issued - either the returned certificate failed to be decoded or an instance of the referenced issuer used for signing failed. No further action will be taken on the `CertificateRequest` by it's controller. | -| True | Issued | A signed certificate has been successfully issued by the referenced `Issuer`. | +| False | Failed | The certificate has failed to be issued - either the returned certificate failed to be decoded or an instance of the referenced issuer used for signing failed. No further action will be taken on the `CertificateRequest` by its controller and it can be considered terminally failed. | +| True | Issued | A signed certificate has been successfully issued by the referenced `Issuer`. | + +This condition should be set by the issuer. + +### Denied +| Denied | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| True | \ | The `CertificateRequest` was Denied by an approver. This `CertificateRequest` can be considered terminally failed. + +This condition should only be set by an approver. + +### Approved +| Approved | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| True | \ | The `CertificateRequest` was approved by the approver. This `CertificateRequest` is approved and can be issued by the issuer. + +This condition should only be set by an approver. + +### InvalidRequest +| InvalidRequest | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| True | \ | The `CertificateRequest` is invalid. This `CertificateRequest` can be considered terminally failed. + ## UserInfo diff --git a/content/docs/contributing/external-issuers.md b/content/docs/contributing/external-issuers.md index 4169a95f89..4d18572fb3 100644 --- a/content/docs/contributing/external-issuers.md +++ b/content/docs/contributing/external-issuers.md @@ -55,14 +55,7 @@ If the `CertificateRequest` is not `Approved`, the issuer **must** not process i responsible for approving `CertificateRequests` and should refuse to proceed if they find a certificate that is not approved. -### Supporting Legacy cert-manager Releases - -Certificate approval was added to cert-manager in `v1.3`. In order to support older versions of cert-manager, -external issuers may choose to sign `CertificateRequests` that will never have an approval -condition set, but this should be feature-gated and disabled by default. - -If you're creating a new External Issuer today, we'd strongly recommend that you do not support such old -versions of cert-manager. +If a `CertificateRequest` created for an issuance associated with a `Certificate` gets [`Denied`](../concepts/certificaterequest.md#approval), the issuance will be failed by cert-manager's issuing controller. ## Conditions diff --git a/content/docs/faq/README.md b/content/docs/faq/README.md index c2b27c0ad1..9bb31f91b5 100644 --- a/content/docs/faq/README.md +++ b/content/docs/faq/README.md @@ -97,13 +97,33 @@ Due to the nature of the Kubernetes event mechanism these will be purged after a -cert-manager will retry a failed issuance except for a few rare edge cases where manual intervention is needed. - -If an issuance fails because of a temporary error, it will be retried again with a short exponential backoff (currently 5 seconds to 5 minutes). A temporary error is one that does not result in a failed `CertificateRequest`. - -If the issuance fails with an error that resulted in a failed `CertificateRequest`, it will be retried with a longer binary exponential backoff (1 hour to 32 hours) to avoid overwhelming external services. - -You can always trigger immediate renewal using the [`cmctl renew` command](../reference/cmctl.md#renew) +cert-manager will retry a failed issuance except for a few rare edge cases where +manual intervention is needed. + +We aim to retry after a short delay in case of ephemeral failures such as +network connection errors and with a longer exponentially increasing delay after +'terminal' failures. + +You can observe that latest issuance has terminally failed if the `Certificate` +has `Issuing` condition set to false and has `status.lastFailureTime` set. In +this case the issuance will be retried after an exponentially increasing delay +(1 to 32 hours) by creating a new `CertficateRequest`. You can trigger an +immediate renewal using the [`cmctl renew` +command](../reference/cmctl.md#renew). Terminal failures occur if the issuer +sets the `CertificateRequest` to failed (for example if CA rejected the request +due to a rate limit being reached) or invalid or if the `CertificateRequest` +gets denied by an approver. + +Ephemeral failures result in the same `CertificateRequest` being re-synced after +a short delay (up to 5 minutes). Typically they can only be observed in +cert-manager controller logs. + +If it appears the issuance has got stuck and `cmctl renew` does not work, you +can delete the latest `CertificateRequest`. This is mostly a harmless action +(the worst that could happen is duplicate issuance if there was a potentially +successful one in progress), but we do aim for this to not be part of user flow- +do reach out if you think you have found a case where the flow could be +improved. ### Is ECC (elliptic-curve cryptography) supported? From e849f0f1b8203a3bfd941900c5d627f51ed60f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 7 Apr 2023 15:30:46 +0200 Subject: [PATCH 018/264] release-1.11: add a section in the release notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- .spelling | 4 ++++ .../docs/release-notes/release-notes-1.11.md | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.spelling b/.spelling index 06298e9023..5494e95ffa 100644 --- a/.spelling +++ b/.spelling @@ -553,6 +553,10 @@ NetworkPolicy mjudeikis rgl 1password +VCert +v1.11.0 +v1.11.1 +v4.4.1 # TEMPORARY # these are temporarily ignored because the spellchecker diff --git a/content/docs/release-notes/release-notes-1.11.md b/content/docs/release-notes/release-notes-1.11.md index 65a014fa55..648be9af6d 100644 --- a/content/docs/release-notes/release-notes-1.11.md +++ b/content/docs/release-notes/release-notes-1.11.md @@ -3,13 +3,26 @@ title: Release 1.11 description: 'cert-manager release notes: cert-manager 1.11' --- +## v1.11.1 + +### Bug or Regression + +- Bump helm and other dependencies to fix CVEs, along with upgrading go and base images ([#5815](https://github.com/cert-manager/cert-manager/pull/5815), [@SgtCoDFish](https://github.com/SgtCoDFish)) +- Bump the distroless base images ([#5930](https://github.com/cert-manager/cert-manager/pull/5930), [@maelvls](https://github.com/maelvls)) +- The auto-retry mechanism added in VCert 4.23.0 and part of cert-manager 1.11.0 ([#5674](https://github.com/cert-manager/cert-manager/pull/5674)) has been found to be faulty. Until this issue is fixed upstream, we now use a patched version of VCert. This patch will slowdown the issuance of certificates by 9% in case of heavy load on TPP. We aim to release at an ulterior date a patch release of cert-manager to fix this slowdown. ([#5819](https://github.com/cert-manager/cert-manager/pull/5819), [@maelvls](https://github.com/maelvls)) +- Use a fake kube-apiserver version when generating helm template in `cmctl x install`, to work around a hardcoded Kubernetes version in Helm. ([#5726](https://github.com/cert-manager/cert-manager/pull/5726), [@SgtCoDFish](https://github.com/SgtCoDFish)) + +### Other + +- Bump keystore-go to v4.4.1 to work around an upstream rewrite of history ([#5730](https://github.com/cert-manager/cert-manager/pull/5730), [@SgtCoDFish](https://github.com/SgtCoDFish)) + +## v1.11.0 + cert-manager `v1.11.0` includes a drastic reduction in cert-manager's runtime memory usage, a slew of improvements to AKS integrations and various other tweaks, fixes and improvements, all towards cert-manager's goal of being the best way to handle certificates in modern Cloud Native applications. -## Major Themes - ### Support for Azure Workload Identity Federation with Azure DNS and ACME DNS-01 cert-manager can now authenticate using [Azure Workload Identity Federation](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation) to manage ACME DNS-01 records in Azure DNS. From 466a3a53ef7acbfd86a5b56d67d313eb0da569c8 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Tue, 11 Apr 2023 12:28:40 +0100 Subject: [PATCH 019/264] add more notes on importing cert-manager as a module Signed-off-by: Ashley Davis --- content/docs/contributing/importing.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/content/docs/contributing/importing.md b/content/docs/contributing/importing.md index cb4e488936..2c39a247e3 100644 --- a/content/docs/contributing/importing.md +++ b/content/docs/contributing/importing.md @@ -30,14 +30,13 @@ aware of this need! We'll always try to avoid breakage where we can. ## Module Import Paths -The original cert-manager repository was created on GitHub as `https://github.com/jetstack/cert-manager`, and was later -migrated to `https://github.com/cert-manager/cert-manager`. +For all supported versions of cert-manager, the module import path is `github.com/cert-manager/cert-manager`. -This means the Go module import path you need depends on the version of cert-manager you're trying to use. +Historically, the cert-manager repository was created on GitHub as `https://github.com/jetstack/cert-manager`, and was later +migrated to `https://github.com/cert-manager/cert-manager`. -For cert-manager 1.8 and later, use the new path: -`github.com/cert-manager/cert-manager` +This means that the Go module import path you need may be different if you're trying to use an older version of cert-manager. +For cert-manager 1.8 and later, use the new path listed above. -For cert-manager 1.7 and earlier, including all point releases, use the old path: -`github.com/jetstack/cert-manager` \ No newline at end of file +For cert-manager versions older than 1.8 use the old path: `github.com/jetstack/cert-manager` From 043c178d48668a38a7fc0e3c8caafff21d61ee60 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Tue, 11 Apr 2023 18:00:30 +0100 Subject: [PATCH 020/264] add workspace + building documentation Signed-off-by: Ashley Davis --- .spelling | 2 ++ content/docs/contributing/building.md | 25 +++++++++++++++++++++++++ package-lock.json | 1 + 3 files changed, 28 insertions(+) diff --git a/.spelling b/.spelling index 06298e9023..ee19ef3002 100644 --- a/.spelling +++ b/.spelling @@ -417,6 +417,8 @@ subcommands subdomain subdomains subfolders +submodule +submodules subresource symlink tamalsaha diff --git a/content/docs/contributing/building.md b/content/docs/contributing/building.md index 010abbcf49..2dfefcdf42 100644 --- a/content/docs/contributing/building.md +++ b/content/docs/contributing/building.md @@ -67,6 +67,29 @@ go version go1.AB.C linux/amd64 go binary used for above version information: go ``` +### Go Workspaces + +In short: Some development tools will complain about cert-manager's module layout; to help with this, generate a +`go.work` file using `make go-workspace`. + +The cert-manager repository as of cert-manager 1.12 contains multiple Go modules, in a setup where only the core module `github.com/cert-manager/cert-manager` +is expected to be imported by third party modules. There are separate modules (which we call submodules), all of which have replace statements for the core +cert-manager module. + +This setup is intentional to convey that these submodules are not intended to be imported by third parties, and to ensure that each submodule always uses +whatever the cert-manager core module version is at the same commit - but this structure can have the side effect that certain development tools and scripts +will not work as expected. + +As an example, `go test ./...` will by default only affect the core module. To test, say, the controller, you'd need to use `cd cmd/controller && go test ./...`. + +This can be avoided through the use of go workspaces, which will handle local replacements for you and work better with editors such as VS Code. + +You can run `make go-workspace` to generate a `go.work` file which should enable `go test ./...` to work across the +whole repo, and which should help editors to understand the module structure. + +Note that go workspaces are not used when testing pull requests in CI. If you see errors in CI which you can't replicate +locally, try deleting the `go.work` file. + ### Parallelism The cert-manager Makefile is designed to be highly parallel wherever possible. Any build and test commands should be able to be executed in parallel using @@ -104,6 +127,8 @@ There are make targets to help with this; see [Developing with Kind](./kind.md) First of all: If you want to test using `go test`, feel free! For unit tests (which we define as any test outside of the `test/` directory), `go test` will work on a fresh checkout. +Note that the cert-manager repo is split into multiple modules and unless you're using go workspaces `go test ./...` won't actually run all tests. See [Go Workspaces](./building.md#go-workspaces) above for more details. + Integration tests may require some external tools to be set up first, so to run the integration tests inside `test/` you might need to run: ```bash diff --git a/package-lock.json b/package-lock.json index 729262c19e..63f381dda1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11857,6 +11857,7 @@ } }, "scripts/gendocs/postprocess": { + "name": "format-api-docs", "version": "1.0.0", "dev": true, "license": "ISC", From 27e47e2c469b2ad77cff8e724d6ec5d45e7ae919 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Wed, 12 Apr 2023 13:43:00 +0100 Subject: [PATCH 021/264] code review feedback Signed-off-by: Ashley Davis --- content/docs/contributing/building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/contributing/building.md b/content/docs/contributing/building.md index 2dfefcdf42..721dd1c97d 100644 --- a/content/docs/contributing/building.md +++ b/content/docs/contributing/building.md @@ -88,7 +88,7 @@ You can run `make go-workspace` to generate a `go.work` file which should enable whole repo, and which should help editors to understand the module structure. Note that go workspaces are not used when testing pull requests in CI. If you see errors in CI which you can't replicate -locally, try deleting the `go.work` file. +locally, try building with the `GOWORK` environment variable set to `off` or deleting the `go.work` file. ### Parallelism From 1568382b951c5a72a19371b70ac76302246ac1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Thu, 13 Apr 2023 10:07:48 +0200 Subject: [PATCH 022/264] move "merging master into release-next" to the README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- README.md | 21 ++++++++++++++ content/docs/contributing/release-process.md | 29 -------------------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index c21150c18e..6601f58100 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,27 @@ Sometimes you'll want to add a comment which is only for documentation maintaine Use `{/* my comment */}` rather than the HTML-style comments you'd normally use for Markdown files. Other comment types will cause errors. +### Task: Merging `master` into `release-next` + +In rare occasions, when writing documentation for an unreleased feature, you may +notice that some recent changes in `master` aren't present in `release-next`. If +that is a problem, you will want to update `release-next` branch with the latest +changes from `master`. To update `release-next` with the changes made to +`master`, follow these steps: + +1. Create a PR to merge `master` into `release-next` using [this magic + link][ff-release-next]. +2. If you see the label `dco-signoff: no`, add a comment on the PR with: + + ```text + /override dco + ``` + + It is necessary because some the merge commits have been written by the bot + and do not have a DCO signoff. + +[ff-release-next]: https://github.com/cert-manager/website/compare/release-next...master?quick_pull=1&title=%5Brelease-next%5D+Merge+master+into+release-next&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco + ## Website Development Tooling First [install nodejs (and package manager called `npm`)](https://nodejs.org/en/). diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 164516778f..b772c59567 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -578,32 +578,3 @@ Other than the different `cert-manager/release` tag and `cmrel` version, you can is used for 1.6 and 1.7 - just remember to change the version of `cmrel` you install! [older-release-process]: https://github.com/cert-manager/website/blob/6fa0db74de0ae17d7be638a08155d1b4e036aaa9/content/en/docs/contributing/release-process.md?plain=1 - -## Documentation Process - -Authors of PRs to cert-manager/website need to check that the "base branch" is -correct: - -| Description | Base branch | Folder | -|--------------------------------------------|----------------|----------------| -| Documentation PR for an existing feature | `master` | `content/docs` | -| Documentation PR for an unreleased feature | `release-next` | `content/docs` | - -In rare occasions, when writing documentation for an unreleased feature, you may -notice that some recent changes in `master` aren't present in `release-next`. If -that is a problem, you will want to update `release-next` branch with the latest -changes from `master`. To update `release-next` with the changes made to -`master`, follow these steps: - -1. Create a PR to merge `master` into `release-next` using [this magic - link][ff-release-next]. -2. If you see the label `dco-signoff: no`, add a comment on the PR with: - - ```text - /override dco - ``` - - It is necessary because some the merge commits have been written by the bot - and do not have a DCO signoff. - -[ff-release-next]: https://github.com/cert-manager/website/compare/release-next...master?quick_pull=1&title=%5Brelease-next%5D+Fast-forward+to+master&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco From f6c88396bc99252c54e0ab9e006a48bc589d880d Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Wed, 12 Apr 2023 16:50:24 +0100 Subject: [PATCH 023/264] update release process to reflect autobuild Signed-off-by: Ashley Davis --- content/docs/contributing/release-process.md | 26 +++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index b772c59567..763876647d 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -225,7 +225,7 @@ page if a step is missing or if it is outdated. > This is only a temporary change to allow you to update the branch. > [Prow will re-apply the branch protection within 24 hours](https://docs.prow.k8s.io/docs/components/optional/branchprotector/#updating). -5. Create the tag for the new release locally and push it upstream: +5. Create the tag for the new release locally and push it upstream (starting the cert-manager build): ```bash RELEASE_VERSION=v1.8.0-beta.0 @@ -240,6 +240,10 @@ page if a step is missing or if it is outdated. permission, you will have to open a PR to merge master into the release branch), and wait for the PR checks to become green. + For recent versions of cert-manager, the tag being pushed will trigger a Google Cloud Build job to start, + kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to + the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). + 6. Generate and edit the release notes: 1. Use the following two tables to understand how to fill in the four @@ -322,22 +326,27 @@ page if a step is missing or if it is outdated. 4. **(final release only)** Check the release notes include all changes since the last final release. -7. Run `cmrel makestage`: +7. Check that the build is complete and send Slack messages about the release: + + 1. For recent versions of cert-manager, the build will have been automatically + triggered by the tag being pushed earlier. You can check if it's complete on + the [GCB Build History](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). - 1. In this example we stage a release using the `v1.8.0-beta.0` git ref: + 2. If you're releasing an older version of cert-manager (earlier than 1.10) then the automatic build + will failed because the GCB config for that build wasn't backported. + In this case, you'll need to trigger the build manually using `cmrel`, which takes about 5 minutes: ```bash # Must be run from the "cert-manager/release" repo folder. cmrel makestage --ref=$RELEASE_VERSION ``` - This step takes ~5 minutes. It will build all container images and create + This build takes ~5 minutes. It will build all container images and create all the manifest files, sign Helm charts and upload everything to a storage bucket on Google Cloud. These artifacts will then be published and released in the next steps. - 2. While the build is running, send a first Slack message to - `#cert-manager-dev`: + 3. In any case, send a first Slack message to `#cert-manager-dev`:

Releasing 1.2.0-alpha.2 🧵 @@ -345,13 +354,12 @@ page if a step is missing or if it is outdated.

🔰 Please have a quick look at the build log as it might contain some unredacted - data that we forgot to redact. We try to make sure the sensitive data is + data that we forgot to hide. We try to make sure the sensitive data is properly redacted but sometimes we forget to update this.

3. Send a second Slack message in reply to this first message with the - Cloud Build job link that `cmrel` displayed in "View logs at". For - example, the message would look like: + Cloud Build job link. For example, the message might look like:

Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237 From 0e509b15aefb0cdbaa3319ea0bbff5c2a2816c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 14 Apr 2023 14:03:31 +0200 Subject: [PATCH 024/264] we settled on 26 april 2023 for the release of 1.12 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/installation/supported-releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index ad08854415..b08ce59187 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -28,7 +28,7 @@ cert-manager expects that ServerSideApply is enabled in the cluster for all vers | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:--------------:|:--------------:|:-----------------------------------:|:---------------------------------:| -| [1.12][] | End of April, 2023 | End of August, 2023 | 1.22 → 1.27 | 4.9 → 4.13 | +| [1.12][] | Apr 26, 2023 | End of August, 2023 | 1.22 → 1.27 | 4.9 → 4.13 | Dates in the future are uncertain and might change. From c605d6b5e1dcf455a7397ecb4c336e0fc79734a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 14 Apr 2023 15:01:06 +0200 Subject: [PATCH 025/264] 1.12 will not be supporting 1.27 on the release date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We aim at backporting the support for 1.27 in a patch release. Signed-off-by: Maël Valais --- content/docs/installation/supported-releases.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index b08ce59187..8e60cca1f5 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -26,9 +26,9 @@ cert-manager expects that ServerSideApply is enabled in the cluster for all vers ## Upcoming releases -| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:--------------:|:--------------:|:-----------------------------------:|:---------------------------------:| -| [1.12][] | Apr 26, 2023 | End of August, 2023 | 1.22 → 1.27 | 4.9 → 4.13 | +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:--------------:|:------------------:|:-----------------------------------:|:---------------------------------:| +| [1.12][] | Apr 26, 2023 | End of August, 2023 | 1.22 → 1.26 | 4.9 → 4.13 | Dates in the future are uncertain and might change. From 0a3d01ab0db336b72bd3cc68181c8e25c146f78a Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 18 Apr 2023 11:26:01 +0100 Subject: [PATCH 026/264] Fix selector.issuerRef example to use map instead of list Fixes: https://github.com/cert-manager/approver-policy/issues/217 Signed-off-by: Richard Wall --- content/docs/projects/approver-policy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/projects/approver-policy.md b/content/docs/projects/approver-policy.md index 935f46cce9..acb4d7ca44 100644 --- a/content/docs/projects/approver-policy.md +++ b/content/docs/projects/approver-policy.md @@ -298,7 +298,7 @@ spec: ... selector: issuerRef: - - name: "my-ca" + name: "my-ca" kind: "*Issuer" group: "cert-manager.io" ``` From c9a1c7366eee6b719246782f82ff1290ed9944ad Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 18 Apr 2023 12:26:01 +0100 Subject: [PATCH 027/264] Copy trust-manager api docs script Signed-off-by: Richard Wall --- scripts/gendocs/generate-approver-policy | 64 ++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100755 scripts/gendocs/generate-approver-policy diff --git a/scripts/gendocs/generate-approver-policy b/scripts/gendocs/generate-approver-policy new file mode 100755 index 0000000000..bbfc7f76b7 --- /dev/null +++ b/scripts/gendocs/generate-approver-policy @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}" + +CRDOC=${REPO_ROOT}/scripts/bin/crdoc + +tmpdir="$(mktemp -d)" + +cleanup() { + echo "+++ Cleaning up tmpdir" + rm -rf "${tmpdir}" +} +trap cleanup EXIT + +checkout() { + ref="$1" + + pushd "$tmpdir" + + echo "+++ Checking out trust-manager ref $ref" + + git fetch origin "$ref" + git reset --hard "$ref" +} + +gendocs() { + outputdir="$1" + + echo "+++ Generating trust-manager reference docs..." + + $CRDOC \ + --resources "$tmpdir/deploy/charts/trust-manager/templates/trust.cert-manager.io_bundles.yaml" \ + --template $REPO_ROOT/scripts/gendocs/templates-trust-manager/markdown.tmpl \ + --output $outputdir +} + +# Note that this script only supports generating a single version of trust-manager API docs, +# since trust-manager is under active development and we wouldn't generally expect older version +# of docs to be useful. The version is also hardcoded for simplicity + +echo "+++ Cloning trust-manager repository..." +git clone "https://github.com/cert-manager/trust-manager.git" "$tmpdir" + +checkout "v0.4.0" + +gendocs "$REPO_ROOT/content/docs/projects/trust-manager/api-reference.md" From 1c47435dab31b8df48b0361dfd19e1895361d380 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 18 Apr 2023 12:26:14 +0100 Subject: [PATCH 028/264] Customize it Signed-off-by: Richard Wall --- scripts/gendocs/generate-approver-policy | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/scripts/gendocs/generate-approver-policy b/scripts/gendocs/generate-approver-policy index bbfc7f76b7..0fdfe4c8c8 100755 --- a/scripts/gendocs/generate-approver-policy +++ b/scripts/gendocs/generate-approver-policy @@ -35,7 +35,7 @@ checkout() { pushd "$tmpdir" - echo "+++ Checking out trust-manager ref $ref" + echo "+++ Checking out ref $ref" git fetch origin "$ref" git reset --hard "$ref" @@ -44,21 +44,17 @@ checkout() { gendocs() { outputdir="$1" - echo "+++ Generating trust-manager reference docs..." + echo "+++ Generating reference docs..." $CRDOC \ - --resources "$tmpdir/deploy/charts/trust-manager/templates/trust.cert-manager.io_bundles.yaml" \ + --resources "$tmpdir/deploy/charts/approver-policy/templates/crds/policy.cert-manager.io_certificaterequestpolicies.yaml" \ --template $REPO_ROOT/scripts/gendocs/templates-trust-manager/markdown.tmpl \ --output $outputdir } -# Note that this script only supports generating a single version of trust-manager API docs, -# since trust-manager is under active development and we wouldn't generally expect older version -# of docs to be useful. The version is also hardcoded for simplicity +echo "+++ Cloning repository..." +git clone "https://github.com/cert-manager/approver-policy.git" "$tmpdir" -echo "+++ Cloning trust-manager repository..." -git clone "https://github.com/cert-manager/trust-manager.git" "$tmpdir" +checkout "main" -checkout "v0.4.0" - -gendocs "$REPO_ROOT/content/docs/projects/trust-manager/api-reference.md" +gendocs "$REPO_ROOT/content/docs/projects/approver-policy/api-reference.md" From 2809bc292275a4a3fc24e3c45dcfca8a1f7abe70 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 18 Apr 2023 12:34:15 +0100 Subject: [PATCH 029/264] Generate the API docs Signed-off-by: Richard Wall --- content/docs/manifest.json | 140 +-- .../README.md} | 0 .../projects/approver-policy/api-reference.md | 978 ++++++++++++++++++ scripts/gendocs/generate-approver-policy | 2 +- .../templates-approver-policy/markdown.tmpl | 97 ++ 5 files changed, 1151 insertions(+), 66 deletions(-) rename content/docs/projects/{approver-policy.md => approver-policy/README.md} (100%) create mode 100644 content/docs/projects/approver-policy/api-reference.md create mode 100644 scripts/gendocs/templates-approver-policy/markdown.tmpl diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 66c12a4c27..e8b2685a6e 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -2,14 +2,15 @@ "routes": [ { "title": "cert-manager", + "routes": [ { "title": "Introduction", "path": "/docs/README.md" }, { - "title": "Getting Started", - "path": "/docs/getting-started/README.md" + "title": "Getting Started", + "path": "/docs/getting-started/README.md" }, { "title": "Installation", @@ -70,8 +71,8 @@ "path": "/docs/installation/upgrading/upgrading-1.10-1.11.md" }, { - "title": "v1.9 to v1.10", - "path": "/docs/installation/upgrading/upgrading-1.9-1.10.md" + "title": "v1.9 to v1.10", + "path": "/docs/installation/upgrading/upgrading-1.9-1.10.md" }, { "title": "v1.8 to v1.9", @@ -347,18 +348,27 @@ }, { "title": "approver-policy", - "path": "/docs/projects/approver-policy.md" + "routes": [ + { + "title": "Introduction", + "path": "/docs/projects/approver-policy/README.md" + }, + { + "title": "API Reference", + "path": "/docs/projects/approver-policy/api-reference.md" + } + ] }, { "title": "trust-manager", "routes": [ { - "title": "Introduction", - "path": "/docs/projects/trust-manager/README.md" + "title": "Introduction", + "path": "/docs/projects/trust-manager/README.md" }, { - "title": "API Reference", - "path": "/docs/projects/trust-manager/api-reference.md" + "title": "API Reference", + "path": "/docs/projects/trust-manager/api-reference.md" } ] } @@ -372,20 +382,20 @@ "path": "/docs/tutorials/README.md" }, { - "title": "Securing NGINX-ingress", - "path": "/docs/tutorials/acme/nginx-ingress.md" + "title": "Securing NGINX-ingress", + "path": "/docs/tutorials/acme/nginx-ingress.md" }, { - "title": "GKE + Ingress + Let's Encrypt", - "path": "/docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md" + "title": "GKE + Ingress + Let's Encrypt", + "path": "/docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md" }, { - "title": "AKS + LoadBalancer + Let's Encrypt", - "path": "/docs/tutorials/getting-started-aks-letsencrypt/README.md" + "title": "AKS + LoadBalancer + Let's Encrypt", + "path": "/docs/tutorials/getting-started-aks-letsencrypt/README.md" }, { - "title": "Migrating from Kube-LEGO", - "path": "/docs/tutorials/acme/migrating-from-kube-lego.md" + "title": "Migrating from Kube-LEGO", + "path": "/docs/tutorials/acme/migrating-from-kube-lego.md" }, { "title": "Backup and Restore Resources", @@ -421,22 +431,22 @@ } ] }, - { - "title": "Troubleshooting", - "routes": [ - { - "title": "Introduction", - "path": "/docs/troubleshooting/README.md" + { + "title": "Troubleshooting", + "routes": [ + { + "title": "Introduction", + "path": "/docs/troubleshooting/README.md" }, { - "title": "Troubleshooting ACME / Let's Encrypt Certificates", - "path": "/docs/troubleshooting/acme.md" + "title": "Troubleshooting ACME / Let's Encrypt Certificates", + "path": "/docs/troubleshooting/acme.md" }, { - "title": "Troubleshooting webhook", - "path": "/docs/troubleshooting/webhook.md" + "title": "Troubleshooting webhook", + "path": "/docs/troubleshooting/webhook.md" } - ] + ] }, { "title": "FAQ", @@ -549,8 +559,8 @@ "path": "/docs/release-notes/release-notes-1.11.md" }, { - "title": "v1.10", - "path": "/docs/release-notes/release-notes-1.10.md" + "title": "v1.10", + "path": "/docs/release-notes/release-notes-1.10.md" }, { "title": "v1.9", @@ -691,56 +701,56 @@ } ] }, - { - "title": "Reference", - "routes": [ + { + "title": "Reference", + "routes": [ { - "title": "Introduction", - "path": "/docs/reference/README.md" + "title": "Introduction", + "path": "/docs/reference/README.md" }, { - "title": "Command Line Tool (cmctl)", - "path": "/docs/reference/cmctl.md" + "title": "Command Line Tool (cmctl)", + "path": "/docs/reference/cmctl.md" }, - { - "title": "TLS Terminology", - "path": "/docs/reference/tls-terminology.md" + { + "title": "TLS Terminology", + "path": "/docs/reference/tls-terminology.md" }, + { + "title": "Components / Docker Images", + "routes": [ { - "title": "Components / Docker Images", - "routes": [ - { - "title": "Introduction", - "path": "/docs/cli/README.md" + "title": "Introduction", + "path": "/docs/cli/README.md" }, - { - "title": "acmesolver", - "path": "/docs/cli/acmesolver.md" + { + "title": "acmesolver", + "path": "/docs/cli/acmesolver.md" }, - { - "title": "cainjector", - "path": "/docs/cli/cainjector.md" + { + "title": "cainjector", + "path": "/docs/cli/cainjector.md" }, - { - "title": "cmctl", - "path": "/docs/cli/cmctl.md" + { + "title": "cmctl", + "path": "/docs/cli/cmctl.md" }, - { - "title": "controller", - "path": "/docs/cli/controller.md" + { + "title": "controller", + "path": "/docs/cli/controller.md" }, - { - "title": "webhook", - "path": "/docs/cli/webhook.md" + { + "title": "webhook", + "path": "/docs/cli/webhook.md" } - ] + ] }, - { - "title": "API Reference", - "path": "/docs/reference/api-docs.md" + { + "title": "API Reference", + "path": "/docs/reference/api-docs.md" } - ] + ] } ] } diff --git a/content/docs/projects/approver-policy.md b/content/docs/projects/approver-policy/README.md similarity index 100% rename from content/docs/projects/approver-policy.md rename to content/docs/projects/approver-policy/README.md diff --git a/content/docs/projects/approver-policy/api-reference.md b/content/docs/projects/approver-policy/api-reference.md new file mode 100644 index 0000000000..e4d45bb829 --- /dev/null +++ b/content/docs/projects/approver-policy/api-reference.md @@ -0,0 +1,978 @@ +--- +title: approver-policy API Reference +description: "approver-policy API documentation" +--- + +Packages: + +- [`policy.cert-manager.io/v1alpha1`](#policycert-manageriov1alpha1) + +# `policy.cert-manager.io/v1alpha1` + +Resource Types: + + +- [CertificateRequestPolicy](#certificaterequestpolicy) + + + + +## `CertificateRequestPolicy` + + + + + +CertificateRequestPolicy is an object for describing a "policy profile" that makes decisions on whether applicable CertificateRequests should be approved or denied. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringpolicy.cert-manager.io/v1alpha1true
kindstringCertificateRequestPolicytrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy.
+
false
statusobject + CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy.
+
false
+ + +### `CertificateRequestPolicy.spec` + + +CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
selectorobject + Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation.
+
true
allowedobject + Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible.
+
false
constraintsobject + Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute.
+
false
pluginsmap[string]object + Plugins define a set of plugins and their configuration that should be executed when this policy is evaluated against a CertificateRequest. A plugin must already be built within approver-policy for it to be available.
+
false
+ + +### `CertificateRequestPolicy.spec.selector` + + +Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
issuerRefobject + IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". + The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ```
+
false
namespaceobject + Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected.
+
false
+ + +### `CertificateRequestPolicy.spec.selector.issuerRef` + + +IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". + The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ``` + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
groupstring + Group is the wildcard selector to match the `spec.issuerRef.group` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
+
false
kindstring + Kind is the wildcard selector to match the `spec.issuerRef.kind` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
+
false
namestring + Name is the wildcard selector to match the `spec.issuerRef.name` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
+
false
+ + +### `CertificateRequestPolicy.spec.selector.namespace` + + +Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchLabelsmap[string]string + MatchLabels is the set of Namespace labels that select on CertificateRequests which have been created in a Namespace matching the selector.
+
false
matchNames[]string + MatchNames are the set of Namespace names that select on CertificateRequests that have been created in a matching Namespace. Accepts wildcards "*".
+
false
+ + +### `CertificateRequestPolicy.spec.allowed` + + +Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
commonNameobject + CommonName defines the X.509 Common Name that is permissible.
+
false
dnsNamesobject + DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*".
+
false
emailAddressesobject + EmailAddresses defines the X.509 Email SANs that may be requested for.
+
false
ipAddressesobject + IPAddresses defines the X.509 IP SANs that may be requested for.
+
false
isCAboolean + IsCA defines whether it is permissible for a CertificateRequest to have the `spec.IsCA` field set to `true`. An omitted field, value of `nil` or `false`, forbids the `spec.IsCA` field from bring `true`. A value of `true` permits CertificateRequests setting the `spec.IsCA` field to `true`.
+
false
subjectobject + Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested.
+
false
urisobject + URIs defines the X.509 URI SANs that may be requested for.
+
false
usages[]enum + Usages defines the list of permissible key usages that may appear on the CertificateRequest `spec.keyUsages` field. An omitted field or value of `nil` forbids any Usages being requested. An empty slice `[]` is equivalent to `nil`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.commonName` + + +CommonName defines the X.509 Common Name that is permissible. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
+
false
valuestring + Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.dnsNames` + + +DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*". + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.emailAddresses` + + +EmailAddresses defines the X.509 Email SANs that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.ipAddresses` + + +IPAddresses defines the X.509 IP SANs that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject` + + +Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
countriesobject + Countries define the X.509 Subject Countries that may be requested for.
+
false
localitiesobject + Localities defines the X.509 Subject Localities that may be requested for.
+
false
organizationalUnitsobject + OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for.
+
false
organizationsobject + Organizations define the X.509 Subject Organizations that may be requested for.
+
false
postalCodesobject + PostalCodes defines the X.509 Subject Postal Codes that may be requested for.
+
false
provincesobject + Provinces defines the X.509 Subject Provinces that may be requested for.
+
false
serialNumberobject + SerialNumber defines the X.509 Subject Serial Number that may be requested for.
+
false
streetAddressesobject + StreetAddresses defines the X.509 Subject Street Addresses that may be requested for.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.countries` + + +Countries define the X.509 Subject Countries that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.localities` + + +Localities defines the X.509 Subject Localities that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.organizationalUnits` + + +OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.organizations` + + +Organizations define the X.509 Subject Organizations that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.postalCodes` + + +PostalCodes defines the X.509 Subject Postal Codes that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.provinces` + + +Provinces defines the X.509 Subject Provinces that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.serialNumber` + + +SerialNumber defines the X.509 Subject Serial Number that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
+
false
valuestring + Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.streetAddresses` + + +StreetAddresses defines the X.509 Subject Street Addresses that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.uris` + + +URIs defines the X.509 URI SANs that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.constraints` + + +Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
maxDurationstring + MaxDuration defines the maximum duration a certificate may be requested for. Values are inclusive (i.e. a max value of `1h` will accept a duration of `1h`). MaxDuration and MinDuration may be the same value. An omitted field or value of `nil` permits any maximum duration. If MaxDuration is defined, a duration _must_ be requested on the CertificateRequest.
+
false
minDurationstring + MinDuration defines the minimum duration a certificate may be requested for. Values are inclusive (i.e. a min value of `1h` will accept a duration of `1h`). MinDuration and MaxDuration may be the same value. An omitted field or value of `nil` permits any minimum duration. If MinDuration is defined, a duration _must_ be requested on the CertificateRequest.
+
false
privateKeyobject + PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor.
+
false
+ + +### `CertificateRequestPolicy.spec.constraints.privateKey` + + +PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
algorithmenum + Algorithm defines the allowed crypto algorithm that is used by the requestor for their private key in their request. An omitted field or value of `nil` permits any Algorithm.
+
+ Enum: RSA, ECDSA, Ed25519
+
false
maxSizeinteger + MaxSize defines the maximum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MaxSize and MinSize may be the same value. An omitted field or value of `nil` permits any maximum size.
+
false
minSizeinteger + MinSize defines the minimum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MinSize and MaxSize may be the same value. An omitted field or value of `nil` permits any minimum size.
+
false
+ + +### `CertificateRequestPolicy.spec.plugins[key]` + + +CertificateRequestPolicyPluginData is configuration needed by the plugin approver to evaluate a CertificateRequest on this policy. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
valuesmap[string]string + Values define a set of well-known, to the plugin, key value pairs that are required for the plugin to successfully evaluate a request based on this policy.
+
false
+ + +### `CertificateRequestPolicy.status` + + +CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
conditions[]object + List of status conditions to indicate the status of the CertificateRequestPolicy. Known condition types are `Ready`.
+
false
+ + +### `CertificateRequestPolicy.status.conditions[index]` + + +CertificateRequestPolicyCondition contains condition information for a CertificateRequestPolicyStatus. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
statusstring + Status of the condition, one of ('True', 'False', 'Unknown').
+
true
typestring + Type of the condition, known values are (`Ready`).
+
true
lastTransitionTimestring + LastTransitionTime is the timestamp corresponding to the last status change of this condition.
+
+ Format: date-time
+
false
messagestring + Message is a human readable description of the details of the last transition, complementing reason.
+
false
observedGenerationinteger + If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the CertificateRequestPolicy.
+
+ Format: int64
+
false
reasonstring + Reason is a brief machine readable explanation for the condition's last transition.
+
false
diff --git a/scripts/gendocs/generate-approver-policy b/scripts/gendocs/generate-approver-policy index 0fdfe4c8c8..6d1a965212 100755 --- a/scripts/gendocs/generate-approver-policy +++ b/scripts/gendocs/generate-approver-policy @@ -48,7 +48,7 @@ gendocs() { $CRDOC \ --resources "$tmpdir/deploy/charts/approver-policy/templates/crds/policy.cert-manager.io_certificaterequestpolicies.yaml" \ - --template $REPO_ROOT/scripts/gendocs/templates-trust-manager/markdown.tmpl \ + --template $REPO_ROOT/scripts/gendocs/templates-approver-policy/markdown.tmpl \ --output $outputdir } diff --git a/scripts/gendocs/templates-approver-policy/markdown.tmpl b/scripts/gendocs/templates-approver-policy/markdown.tmpl new file mode 100644 index 0000000000..0fc0fc18e0 --- /dev/null +++ b/scripts/gendocs/templates-approver-policy/markdown.tmpl @@ -0,0 +1,97 @@ +--- +title: approver-policy API Reference +description: "approver-policy API documentation" +--- + +Packages: +{{range .Groups}} +- [`{{.Group}}/{{.Version}}`](#{{ anchorize (printf "%s/%s" .Group .Version) }}) +{{- end -}}{{/* range .Groups */}} + +{{- range .Groups }} +{{- $group := . }} + +# `{{.Group}}/{{.Version}}` + +Resource Types: + +{{range .Kinds}} +- [{{.Name}}](#{{ anchorize .Name }}) +{{end}}{{/* range .Kinds */}} + +{{range .Kinds}} +{{$kind := .}} +## `{{.Name}}` + +{{range .Types}} + +{{if not .IsTopLevel}} +### `{{.Name}}` +{{end}} + +{{.Description}} + + + + + + + + + + + + {{- if .IsTopLevel -}} + + + + + + + + + + + + + + + + + + + {{- end -}} + {{- range .Fields -}} + + + + + + + {{- end -}} + +
NameTypeDescriptionRequired
apiVersionstring{{$group.Group}}/{{$group.Version}}true
kindstring{{$kind.Name}}true
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
{{if .TypeKey}}{{.Name}}{{else}}{{.Name}}{{end}}{{.Type}} + {{.Description}}
+ {{- if or .Schema.Format .Schema.Enum .Schema.Default .Schema.Minimum .Schema.Maximum }} +
+ {{- end}} + {{- if .Schema.Format }} + Format: {{ .Schema.Format }}
+ {{- end }} + {{- if .Schema.Enum }} + Enum: {{ .Schema.Enum | toStrings | join ", " }}
+ {{- end }} + {{- if .Schema.Default }} + Default: {{ .Schema.Default }}
+ {{- end }} + {{- if .Schema.Minimum }} + Minimum: {{ .Schema.Minimum }}
+ {{- end }} + {{- if .Schema.Maximum }} + Maximum: {{ .Schema.Maximum }}
+ {{- end }} +
{{.Required}}
+ +{{- end}}{{/* range .Types */}} +{{- end}}{{/* range .Kinds */}} +{{- end}}{{/* range .Groups */}} From 08f49b6fda5bb6d7f269f5fda76307ee648a1e12 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 18 Apr 2023 12:48:33 +0100 Subject: [PATCH 030/264] Bypass the spell check for approver-policy API reference Signed-off-by: Richard Wall --- package.json | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index d78fcaebda..404188b1d9 100644 --- a/package.json +++ b/package.json @@ -25,26 +25,16 @@ "check": "concurrently --group --timings npm:check:* # Run all the npm check:* scripts in parallel", "check:next-lint": "next lint", "check:links": "find content/docs -type f -name '*.md' | xargs markdown-link-check --quiet --config markdown-link-check.json 2>&1 | awk -v RS=FILE: '/ERROR/{f=1; print RS $0} END{exit f}' # Split into records based on the word FILE and print only records containing word ERROR", - "check:spelling": "FORCE_COLOR=1 mdspell --report --en-us --ignore-numbers --ignore-acronyms 'content/**/*.md' 'content/**/*.html' '_layouts/*.html' '_includes/*.html' '*.html' '!**/api-docs.md' # Force color output in mdspell. # See https://github.com/lukeapage/node-markdown-spellcheck/issues/36#issuecomment-482649408 ", + "check:spelling": "FORCE_COLOR=1 mdspell --report --en-us --ignore-numbers --ignore-acronyms 'content/**/*.md' 'content/**/*.html' '_layouts/*.html' '_includes/*.html' '*.html' '!**/api-docs.md' '!content/docs/projects/approver-policy/api-reference.md' # Force color output in mdspell. # See https://github.com/lukeapage/node-markdown-spellcheck/issues/36#issuecomment-482649408 ", "check:markdown": "remark --rc-path .remarkrc --frail --quiet content/" }, "lint-staged": { - "*.{scss}": [ - "stylelint --fix" - ], - "*.{js,json,jsx,md,mdx}": [ - "prettier --write" - ], - "*.{js,jsx,md,mdx}": [ - "eslint --cache --fix" - ] + "*.{scss}": ["stylelint --fix"], + "*.{js,json,jsx,md,mdx}": ["prettier --write"], + "*.{js,jsx,md,mdx}": ["eslint --cache --fix"] }, "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], + "production": [">0.2%", "not dead", "not op_mini all"], "development": [ "last 1 chrome version", "last 1 firefox version", From 16e60eaa5b2eae5fd47d81c21da410ceba733830 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 18 Apr 2023 12:53:38 +0100 Subject: [PATCH 031/264] Fix broken links Signed-off-by: Richard Wall --- content/docs/projects/README.md | 2 +- content/docs/projects/approver-policy/README.md | 12 ++++++------ content/docs/projects/csi-driver.md | 2 +- content/docs/usage/approver-policy.md | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/content/docs/projects/README.md b/content/docs/projects/README.md index 9479756cb5..185df2fce4 100644 --- a/content/docs/projects/README.md +++ b/content/docs/projects/README.md @@ -11,7 +11,7 @@ These tools help with security, compliance and control. - [istio-csr](./istio-csr.md): Secure Istio service mesh with istio-csr which is an agent that allows for [Istio](https://istio.io) workload and control plane components to be secured using cert-manager. -- [approver-policy](./approver-policy.md): +- [approver-policy](./approver-policy/README.md): a cert-manager **approver** that will automatically approve or deny certificate requests based on defined policy. - [csi-driver](./csi-driver.md): diff --git a/content/docs/projects/approver-policy/README.md b/content/docs/projects/approver-policy/README.md index acb4d7ca44..d17c81a9f5 100644 --- a/content/docs/projects/approver-policy/README.md +++ b/content/docs/projects/approver-policy/README.md @@ -4,18 +4,18 @@ description: 'Policy plugin for cert-manager' --- approver-policy is a cert-manager -[approver](../concepts/certificaterequest.md#approval) +[approver](../../concepts/certificaterequest.md#approval) that will approve or deny CertificateRequests based on policies defined in the `CertificateRequestPolicy` custom resource. ## Installation -[cert-manager](../installation/README.md) is a dependency of approver-policy. +[cert-manager](../../installation/README.md) is a dependency of approver-policy. > ⚠️ > > It is important that the -> [default approver is disabled in cert-manager](../concepts/certificaterequest.md#approver-controller). +> [default approver is disabled in cert-manager](../../concepts/certificaterequest.md#approver-controller). > If the default approver is not disabled in cert-manager, approver-policy will > race with cert-manager and policy will be ineffective. > @@ -33,10 +33,10 @@ helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manag ``` If you are using approver-policy with [external -issuers](../configuration/external.md), you _must_ +issuers](../../configuration/external.md), you _must_ include their signer names so that approver-policy has permissions to approve and deny CertificateRequests that -[reference them](../concepts/certificaterequest.md#rbac-syntax). +[reference them](../../concepts/certificaterequest.md#rbac-syntax). For example, if using approver-policy for the internal issuer types, along with [google-cas-issuer](https://github.com/jetstack/google-cas-issuer), and [aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer), @@ -71,7 +71,7 @@ request, the request is denied.** A denied CertificateRequest is considered to be permanently failed. If it was created for a Certificate resource, the issuance will be retried with -[exponential backoff](../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) +[exponential backoff](../../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) like all other permanent issuance failures. A CertificateRequest that is neither approved nor denied (because no matching policy was found) will not be further processed by cert-manager until it gets either approved or denied. diff --git a/content/docs/projects/csi-driver.md b/content/docs/projects/csi-driver.md index ab9cc84f60..31f7f1c74d 100644 --- a/content/docs/projects/csi-driver.md +++ b/content/docs/projects/csi-driver.md @@ -196,7 +196,7 @@ volumeAttributes: If the flag `--use-token-request` is enabled on the csi-driver DaemonSet, the [CertificateRequest](../concepts/certificaterequest.md) resource will be created by the mounting Pod's ServiceAccount. This can be pared with -[approver-policy](./approver-policy.md) to enable advanced policy on a per +[approver-policy](./approver-policy/README.md) to enable advanced policy on a per ServiceAccount basis. Ensure to give permissions to Pod ServiceAccounts to create CertificateRequests diff --git a/content/docs/usage/approver-policy.md b/content/docs/usage/approver-policy.md index 09d5768f3d..b712d04b7f 100644 --- a/content/docs/usage/approver-policy.md +++ b/content/docs/usage/approver-policy.md @@ -10,5 +10,5 @@ API](../concepts/certificaterequest.md#approval). cert-manager project that enables you to write policy to automatically manage this approval mechanism. -Please read the [project page](../projects/approver-policy.md) for more +Please read the [project page](../projects/approver-policy/README.md) for more information on how to install and use approver-policy. From 9462b7d8f3f0ab56b795b1c29aa441f8de01f6cc Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 18 Apr 2023 12:59:53 +0100 Subject: [PATCH 032/264] Link to the API reference from the approver-policy intro page Signed-off-by: Richard Wall --- content/docs/projects/approver-policy/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/docs/projects/approver-policy/README.md b/content/docs/projects/approver-policy/README.md index d17c81a9f5..d8efc41963 100644 --- a/content/docs/projects/approver-policy/README.md +++ b/content/docs/projects/approver-policy/README.md @@ -383,3 +383,7 @@ spec: ``` There are currently no open source plugins. + +## API Reference + +> 📖 Read the [approver-policy API reference](api-reference.md). From 74636909d3d3312d30fb6a4cf3b4e5ad14a88836 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 18 Apr 2023 13:12:37 +0100 Subject: [PATCH 033/264] npm exec prettier -- -w package.json Signed-off-by: Richard Wall --- package.json | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 404188b1d9..1b062a0eb3 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,22 @@ "check:markdown": "remark --rc-path .remarkrc --frail --quiet content/" }, "lint-staged": { - "*.{scss}": ["stylelint --fix"], - "*.{js,json,jsx,md,mdx}": ["prettier --write"], - "*.{js,jsx,md,mdx}": ["eslint --cache --fix"] + "*.{scss}": [ + "stylelint --fix" + ], + "*.{js,json,jsx,md,mdx}": [ + "prettier --write" + ], + "*.{js,jsx,md,mdx}": [ + "eslint --cache --fix" + ] }, "browserslist": { - "production": [">0.2%", "not dead", "not op_mini all"], + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], "development": [ "last 1 chrome version", "last 1 firefox version", From 08e028dac3ed6d0947e95c1ca39a5af62bb4fcb7 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 26 Apr 2023 17:15:12 +0100 Subject: [PATCH 034/264] Wrap the cert-manager re-configuration instructions for easier reading And explain that extraArgs and version must be specified to avoid changing the version while reconfiguring cert-manager. Signed-off-by: Richard Wall --- .../docs/projects/approver-policy/README.md | 55 +++++++++++++++---- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/content/docs/projects/approver-policy/README.md b/content/docs/projects/approver-policy/README.md index d8efc41963..aa7efcef6c 100644 --- a/content/docs/projects/approver-policy/README.md +++ b/content/docs/projects/approver-policy/README.md @@ -8,22 +8,55 @@ approver-policy is a cert-manager that will approve or deny CertificateRequests based on policies defined in the `CertificateRequestPolicy` custom resource. -## Installation +## Prerequisites -[cert-manager](../../installation/README.md) is a dependency of approver-policy. +[cert-manager must be installed](../../installation/README.md), and +the [the default approver in cert-manager must be disabled](../../concepts/certificaterequest.md#approver-controller). -> ⚠️ -> -> It is important that the -> [default approver is disabled in cert-manager](../../concepts/certificaterequest.md#approver-controller). -> If the default approver is not disabled in cert-manager, approver-policy will +> ⚠️ If the default approver is not disabled in cert-manager, approver-policy will > race with cert-manager and policy will be ineffective. + +If you install cert-manager using `helm install` or `helm upgrade`, +you can disable the default approver by [Customizing the Chart Before Installing](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing) using the `--set` or `--values` command line flags: + +``` +# Example --set value +--set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver +``` + +```yaml +# Example --values file content +extraArgs: + - "--controllers=*,-certificaterequests-approver" # ⚠ Disable cert-manager's built-in approver +``` + +Here's a full example which will install cert-manager or reconfigure it if it is already installed: + +```terminal +helm upgrade cert-manager jetstack/cert-manager \ + --install \ + --create-namespace \ + --namespace cert-manager \ + --version REPLACE-WITH-YOUR-CERT-MANAGER-VERSION \ + --set installCRDs=true \ + --set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver +``` + +> ℹ️ The `--set installCRDs=true` setting is a convenient way to install the +> cert-manager CRDS, but it is optional and has some drawbacks. +> Read [Helm: Installing Custom Resource Definitions](https://deploy-preview-1216--cert-manager-website.netlify.app/docs/installation/helm/#3-install-customresourcedefinitions) to learn more. > -> ```terminal -> helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set extraArgs={--controllers='*\,-certificaterequests-approver'} --set installCRDs=true --create-namespace -> ``` +> ℹ️ Be sure to customize the cert-manager controller `extraArgs`, +> which are at the top level of the values file. +> *Do not* change the `webhook.extraArgs`, `startupAPICheck.extraArgs` or `cainjector.extraArgs` settings. > -> ⚠️ +> ⚠️ If you are reconfiguring an already installed cert-manager, +> check whether the original installation already customized the `extraArgs` value +> by running `helm get values cert-manager --namespace cert-manager`. +> If there are already `extraArgs` values, merge those with the extra `--controllers` value. +> Otherwise your original `extraArgs` values will be overwritten. + +## Installation To install approver-policy: From 57b8b817b478a38b1535994ed653b3485d5f4874 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Tue, 2 May 2023 11:13:39 +0100 Subject: [PATCH 035/264] add note about linux/arm64 support Signed-off-by: Ashley Davis --- content/docs/contributing/building.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/content/docs/contributing/building.md b/content/docs/contributing/building.md index 721dd1c97d..92fd7a996f 100644 --- a/content/docs/contributing/building.md +++ b/content/docs/contributing/building.md @@ -7,8 +7,11 @@ cert-manager is built and tested using [make](https://www.gnu.org/software/make/ where possible and keeping system dependencies to a minimum. The cert-manager build system can provision most of its dependencies - including Go - automatically if required. -cert-manager's build system fully supports developers who use `Linux amd64`, `macOS amd64` and `macOS arm64`. Other operating systems and architectures may -work, but are largely untested. +cert-manager's build system fully supports developers who use `Linux amd64`, `macOS amd64` and `macOS arm64`. + +It also has limited support for `Linux arm64`, although that platform is largely untested and isn't fully supported. + +Other operating systems and architectures may work but will likely require hacks and workarounds to develop on. ## Prerequisites From 6fb0e3b514c8b9843d29f1d1e4e2b3c3c3199fb3 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Thu, 4 May 2023 09:57:56 +0100 Subject: [PATCH 036/264] add more detail about meetings this was discussed in the community meeting on 2023-05-04 Signed-off-by: Ashley Davis --- content/docs/contributing/README.md | 48 +++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/content/docs/contributing/README.md b/content/docs/contributing/README.md index 2c51a0a0b1..842aa0664e 100644 --- a/content/docs/contributing/README.md +++ b/content/docs/contributing/README.md @@ -13,17 +13,53 @@ some details on how to build, test and run cert-manager for development purposes ## Meetings -All cert-manager meetings are open for everyone to join! +All cert-manager meetings are open for everyone to join; if you have a question or a suggestion or just want to chat, +please feel free to come along and get involved! -To get invites you can subscribe to [our mailing list](https://groups.google.com/forum/#!forum/cert-manager-dev). +To get invites you can subscribe to [our mailing list](https://groups.google.com/forum/#!forum/cert-manager-dev) and +you should receive calendar invites by mail shortly after joining. The complexities of calendars mean that some invites +might be sent multiple times depending on your email and calendar providers and you might get some invites for past +or future meetings which have been rescheduled or edited. Sorry about that! -We have 2 regular repeating meetings: - -* daily stand-up meetings [on Google Meet](https://meet.google.com/eum-fyvt-xpa) at [10:30 London time](http://www.thetimezoneconverter.com/?t=10:30&tz=Europe/London) every weekday -* bi-weekly developer meetings [on Google Meet](https://meet.google.com/iga-jwvx-nye) at [17:00 London time](http://www.thetimezoneconverter.com/?t=17:00&tz=Europe/London) (for dates, check calendar invites or [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U)) +We have 2 regular repeating meetings: our quick daily check-in and a hour-long community meeting every two weeks. If you're having any issues joining our meetings, ensure that you're part of the [`cert-manager-dev`](https://groups.google.com/forum/#!forum/cert-manager-dev) Google group, and always feel free to ask in [Slack](./#slack) for help! +

+🔰 All of our meetings happen on London (UK) time; you can add London to the world clocks on your phone to avoid confusion! + +When daylight savings time changes in London might be different to when it changes for you if you live in a place that either +doesn't have DST or which changes on a different schedule like North America or Australia! +
+ +### Daily Check-In + +Our daily check-in meetings [happen on Google Meet](https://meet.google.com/eum-fyvt-xpa) at [10:30 London time](http://www.thetimezoneconverter.com/?t=10:30&tz=Europe/London) every weekday. + +The format is a 5 minute social chat, followed by a quick round-robin status report and ending with any longer form talking points. + +The status report is a stand-up where we talk about work done yesterday, work coming up and highlight any blockers. +We'll try to keep to a **strict time limit** during these status reports of around 1 minute per person. + +Please don't be offended if someone steps in when you run out of time and moves the reports along to the next person - the idea +is for everyone to be succinct so it's clear what's being worked on and who is blocked. + +We finish with talking points, which are open-ended discussions on any topic related to cert-manager or its sub-projects. +We'll ensure that anyone outside of the core maintainer team who has a talking point goes first. + +### Community Meetings + +Our bi-weekly (i.e. every two weeks) community meetings happen [on Google Meet](https://meet.google.com/iga-jwvx-nye) at [17:00 London time](http://www.thetimezoneconverter.com/?t=17:00&tz=Europe/London) (for dates, check calendar invites or [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U)). + +These meetings are an hour-long chat about cert-manager topics. It's a great way to get involved with contributing for the +first time; to get answers to any questions you might have; or to propose a new feature which needs some explanation. + +If you want to discuss something, please add it to the [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U) +before the meeting. The meeting chair will try to get to everything that was on the notes before the meeting started. + +We try to record these meetings and put them on YouTube so they can be checked later - if you don't want to appear on video please keep +your camera off! + ## Slack We have two cert-manager channels on [Kubernetes Slack](https://slack.k8s.io) which we use to chat: From e9dfc428aea9a21f06ab91345d7f04395e83d202 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Thu, 4 May 2023 10:00:07 +0100 Subject: [PATCH 037/264] change description of GSoD since it's long past, it's better to update it to past tense so the site doesn't look unmaintained :D Signed-off-by: Ashley Davis --- content/docs/contributing/README.md | 6 ------ content/docs/contributing/google-season-of-docs/README.md | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/content/docs/contributing/README.md b/content/docs/contributing/README.md index 842aa0664e..714ac45a32 100644 --- a/content/docs/contributing/README.md +++ b/content/docs/contributing/README.md @@ -66,9 +66,3 @@ We have two cert-manager channels on [Kubernetes Slack](https://slack.k8s.io) wh * [`cert-manager`](https://kubernetes.slack.com/messages/cert-manager): for all users of cert-manager; use this one for any usage related questions * [`cert-manager-dev`](https://kubernetes.slack.com/messages/cert-manager-dev): for collaboration between cert-manager contributors and maintainers; please only use this for code related questions - -## Google Season of Docs 2022 - -The cert-manager team are participating in [Google Season of Docs 2022](https://developers.google.com/season-of-docs)! - -Check out our [2022 Project Proposals](./google-season-of-docs/2022/README.md) if you want to get involved! diff --git a/content/docs/contributing/google-season-of-docs/README.md b/content/docs/contributing/google-season-of-docs/README.md index bde3b77e43..cb43f69aa8 100644 --- a/content/docs/contributing/google-season-of-docs/README.md +++ b/content/docs/contributing/google-season-of-docs/README.md @@ -1,8 +1,8 @@ --- title: Google Season of Docs -description: Google season of docs 2022 proposal +description: cert-manager and Google Season of Docs --- -The cert-manager organization has registered for the Google Season of Docs! +The cert-manager project participated in Google Season of Docs 2022 -Check out our [2022](./2022/README.md) proposals! +If you're interested in what happened, you can check out our [2022 proposals!](./2022/README.md). From 4391aa95031c62dd6fd2029a78088c5f5ffa190a Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Thu, 4 May 2023 13:44:27 +0200 Subject: [PATCH 038/264] Fix typo Co-authored-by: Richard Wall Signed-off-by: Ashley Davis --- content/docs/contributing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/contributing/README.md b/content/docs/contributing/README.md index 714ac45a32..aec6b21254 100644 --- a/content/docs/contributing/README.md +++ b/content/docs/contributing/README.md @@ -21,7 +21,7 @@ you should receive calendar invites by mail shortly after joining. The complexit might be sent multiple times depending on your email and calendar providers and you might get some invites for past or future meetings which have been rescheduled or edited. Sorry about that! -We have 2 regular repeating meetings: our quick daily check-in and a hour-long community meeting every two weeks. +We have 2 regular repeating meetings: our quick daily check-in and an hour-long community meeting every two weeks. If you're having any issues joining our meetings, ensure that you're part of the [`cert-manager-dev`](https://groups.google.com/forum/#!forum/cert-manager-dev) Google group, and always feel free to ask in [Slack](./#slack) for help! From 569484cbe14d10af4548b0dd9cfa44a5a732ff59 Mon Sep 17 00:00:00 2001 From: Chulmin Kang Date: Mon, 8 May 2023 18:13:17 +0900 Subject: [PATCH 039/264] Fix a typo in usage/certificate docs Signed-off-by: Chulmin Kang --- content/docs/usage/certificate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index 7586f9cb95..17792853bf 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -142,7 +142,7 @@ present while waiting for the issuance of a signed certificate when serving. To facilitate this, if the following annotation: ```yaml - cert-manager.io/issue-temporary-certificate": "true" + cert-manager.io/issue-temporary-certificate: "true" ``` is present on the certificate, a self-signed temporary certificate will be From 75cb62aac4ddb9d0c40b8487e652af3e556e8d5a Mon Sep 17 00:00:00 2001 From: Thomas Nguyen Date: Fri, 12 May 2023 14:48:37 -0700 Subject: [PATCH 040/264] Fix leading whitespace production-issuer.yaml Signed-off-by: Thomas Nguyen --- .../acme/example/production-issuer.yaml | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/content/docs/tutorials/acme/example/production-issuer.yaml b/content/docs/tutorials/acme/example/production-issuer.yaml index 38933376db..f7676f2522 100644 --- a/content/docs/tutorials/acme/example/production-issuer.yaml +++ b/content/docs/tutorials/acme/example/production-issuer.yaml @@ -1,18 +1,18 @@ - apiVersion: cert-manager.io/v1 - kind: Issuer - metadata: - name: letsencrypt-prod - spec: - acme: - # The ACME server URL - server: https://acme-v02.api.letsencrypt.org/directory - # Email address used for ACME registration - email: user@example.com - # Name of a secret used to store the ACME account private key - privateKeySecretRef: - name: letsencrypt-prod - # Enable the HTTP-01 challenge provider - solvers: - - http01: - ingress: - class: nginx +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-prod +spec: + acme: + # The ACME server URL + server: https://acme-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-prod + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + class: nginx From 80e45cea5374c15fe784b2544e2e5d14b22b5416 Mon Sep 17 00:00:00 2001 From: Thomas Nguyen Date: Fri, 12 May 2023 14:51:12 -0700 Subject: [PATCH 041/264] Fix leading whitespaces in staging-issuer.yaml Signed-off-by: Thomas Nguyen --- .../acme/example/staging-issuer.yaml | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/content/docs/tutorials/acme/example/staging-issuer.yaml b/content/docs/tutorials/acme/example/staging-issuer.yaml index 064eb04688..b733952029 100644 --- a/content/docs/tutorials/acme/example/staging-issuer.yaml +++ b/content/docs/tutorials/acme/example/staging-issuer.yaml @@ -1,18 +1,18 @@ - apiVersion: cert-manager.io/v1 - kind: Issuer - metadata: - name: letsencrypt-staging - spec: - acme: - # The ACME server URL - server: https://acme-staging-v02.api.letsencrypt.org/directory - # Email address used for ACME registration - email: user@example.com - # Name of a secret used to store the ACME account private key - privateKeySecretRef: - name: letsencrypt-staging - # Enable the HTTP-01 challenge provider - solvers: - - http01: - ingress: - class: nginx +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + class: nginx From ba8fb5550a319ccdf478b7cff70679411f8d6b5e Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 15 May 2023 15:47:34 +0100 Subject: [PATCH 042/264] Add inteon to OWNERS Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- OWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/OWNERS b/OWNERS index 2f608d71a0..f45d54a300 100644 --- a/OWNERS +++ b/OWNERS @@ -8,3 +8,4 @@ approvers: - SgtCoDFish - jahrlin - wallrj +- inteon From b9e6dbae418fbc4661d148dcc288fba922d4428f Mon Sep 17 00:00:00 2001 From: irbekrm Date: Mon, 15 May 2023 19:54:24 +0100 Subject: [PATCH 043/264] Adds a link to example approver-policy plugin implementation Signed-off-by: irbekrm --- content/docs/projects/approver-policy/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/docs/projects/approver-policy/README.md b/content/docs/projects/approver-policy/README.md index aa7efcef6c..014e0930a0 100644 --- a/content/docs/projects/approver-policy/README.md +++ b/content/docs/projects/approver-policy/README.md @@ -417,6 +417,10 @@ spec: There are currently no open source plugins. +If you want to implement an external approver policy plugin take a look at the +example implementation at +https://github.com/cert-manager/example-approver-policy-plugin. + ## API Reference > 📖 Read the [approver-policy API reference](api-reference.md). From 53d004b240b5c79b56842557a9cfbba7db9ed697 Mon Sep 17 00:00:00 2001 From: irbekrm Date: Fri, 19 May 2023 14:08:25 +0100 Subject: [PATCH 044/264] Adds info about cmctl tag Signed-off-by: irbekrm --- content/docs/contributing/release-process.md | 34 +++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 763876647d..248c640de0 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -225,7 +225,7 @@ page if a step is missing or if it is outdated. > This is only a temporary change to allow you to update the branch. > [Prow will re-apply the branch protection within 24 hours](https://docs.prow.k8s.io/docs/components/optional/branchprotector/#updating). -5. Create the tag for the new release locally and push it upstream (starting the cert-manager build): +5. Create the required tags for the new release locally and push it upstream (starting the cert-manager build): ```bash RELEASE_VERSION=v1.8.0-beta.0 @@ -244,7 +244,19 @@ page if a step is missing or if it is outdated. kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). -6. Generate and edit the release notes: +6. Ensure that cmctl refers to the latest tag of cert-manager: + +Bump cert-manager version in [cmctl `go.mod` file](https://github.com/cert-manager/cert-manager/blob/v1.12.0/cmd/ctl/go.mod#L15) and cherry-pick the commit to the release branch. + +Add the tag for cmctl: + ```bash + # This tag is required to be able to go install cmctl + # See https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 + git tag -m"cmd/ctl/$RELEASE_VERSION" "cmd/ctl/$RELASE_VERSION" + git push origin "cmd/ctl/$RELEASE_VERSION" + ``` + +7. Generate and edit the release notes: 1. Use the following two tables to understand how to fill in the four environment variables needed for the next step. These four environment @@ -326,7 +338,7 @@ page if a step is missing or if it is outdated. 4. **(final release only)** Check the release notes include all changes since the last final release. -7. Check that the build is complete and send Slack messages about the release: +8. Check that the build is complete and send Slack messages about the release: 1. For recent versions of cert-manager, the build will have been automatically triggered by the tag being pushed earlier. You can check if it's complete on @@ -365,7 +377,7 @@ page if a step is missing or if it is outdated. Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237

-8. Run `cmrel publish`: +9. Run `cmrel publish`: 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are valid. Run the following command: @@ -410,7 +422,7 @@ page if a step is missing or if it is outdated. Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237

-9. Publish the GitHub release: +10. Publish the GitHub release: 1. Visit the draft GitHub release and paste in the release notes that you generated earlier. You will need to manually edit the content to match @@ -425,7 +437,7 @@ page if a step is missing or if it is outdated. 4. Click "Publish" to make the GitHub release live. -10. Merge the pull request containing the Helm chart: +11. Merge the pull request containing the Helm chart: The Helm charts for cert-manager are served using Cloudflare pages and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). @@ -437,10 +449,10 @@ page if a step is missing or if it is outdated. 4. Merge the PR 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). -11. **(final release only)** Add the new final release to the +12. **(final release only)** Add the new final release to the [supported-releases](../installation/supported-releases.md) page. -12. Open a PR for a [Homebrew](https://brew.sh) formula update for `cmctl`. +13. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. Assuming you have `brew` installed, you can use the `brew bump-formula-pr` command to do this. You'll need the new tag name and the commit hash of that @@ -457,7 +469,7 @@ page if a step is missing or if it is outdated. against https://github.com/homebrew/homebrew-core has been opened, continue with further release steps. -13. Post a Slack message as an answer to the first message. Toggle the check +14. Post a Slack message as an answer to the first message. Toggle the check box "Also send to `#cert-manager-dev`" so that the message is well visible. Also cross-post the message on `#cert-manager`. @@ -465,7 +477,7 @@ page if a step is missing or if it is outdated. https://github.com/cert-manager/cert-manager/releases/tag/v1.0.0 🎉

-14. **(final release only)** Show the release to the world: +15. **(final release only)** Show the release to the world: 1. Send an email to [`cert-manager-dev@googlegroups.com`](https://groups.google.com/g/cert-manager-dev) @@ -478,7 +490,7 @@ page if a step is missing or if it is outdated. 3. Send a toot from the cert-manager Mastodon account! Login details are in Jetstack's 1password (for now). ([Example toot](https://infosec.exchange/@CertManager/109666434738850493)) -15. Proceed to the post-release steps: +16. Proceed to the post-release steps: 1. **(initial beta only)** Create a PR on [cert-manager/release](https://github.com/cert-manager/release) in order to From 505b1b70285ffd57067b9b95665dab38562eda7f Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Fri, 19 May 2023 15:16:32 +0100 Subject: [PATCH 045/264] Update trust-manager CRD path and bump to v0.5.0 We changed the Helm CRD[1] because we made it optional at install time, and in turn added a 'plain' CRD at a different path. [1] https://github.com/cert-manager/trust-manager/pull/102 Signed-off-by: Ashley Davis --- scripts/gendocs/generate-trust-manager | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/gendocs/generate-trust-manager b/scripts/gendocs/generate-trust-manager index bbfc7f76b7..b7168bcc96 100755 --- a/scripts/gendocs/generate-trust-manager +++ b/scripts/gendocs/generate-trust-manager @@ -47,7 +47,7 @@ gendocs() { echo "+++ Generating trust-manager reference docs..." $CRDOC \ - --resources "$tmpdir/deploy/charts/trust-manager/templates/trust.cert-manager.io_bundles.yaml" \ + --resources "$tmpdir/deploy/crds/trust.cert-manager.io_bundles.yaml" \ --template $REPO_ROOT/scripts/gendocs/templates-trust-manager/markdown.tmpl \ --output $outputdir } @@ -59,6 +59,6 @@ gendocs() { echo "+++ Cloning trust-manager repository..." git clone "https://github.com/cert-manager/trust-manager.git" "$tmpdir" -checkout "v0.4.0" +checkout "v0.5.0" gendocs "$REPO_ROOT/content/docs/projects/trust-manager/api-reference.md" From 8a6a86b0a79f030b40e1e18826354cdb5404fbaa Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Fri, 19 May 2023 15:18:44 +0100 Subject: [PATCH 046/264] tweak some language / add JKS details JKS support is a major feature in v0.5.0 Signed-off-by: Ashley Davis --- .spelling | 2 + content/docs/projects/trust-manager/README.md | 31 +++-- .../projects/trust-manager/api-reference.md | 114 ++++++++++++++++++ 3 files changed, 139 insertions(+), 8 deletions(-) diff --git a/.spelling b/.spelling index 45f3770dac..433f8f210c 100644 --- a/.spelling +++ b/.spelling @@ -54,6 +54,7 @@ ACLs ACMEDNS ACMEv1 ACMEv2 +AdditionalFormats AKS ALB APIService @@ -176,6 +177,7 @@ JoshVanL Jsonnet Juneezee JoooostB +KeySelector KUARD Kirill-Garbar Knative diff --git a/content/docs/projects/trust-manager/README.md b/content/docs/projects/trust-manager/README.md index 30e388fbe1..5882feb966 100644 --- a/content/docs/projects/trust-manager/README.md +++ b/content/docs/projects/trust-manager/README.md @@ -26,14 +26,14 @@ if needed. ## Usage -trust-manager is intentionally simple, and adds one new Kubernetes `CustomResourceDefintion`: `Bundle`. +trust-manager is intentionally simple, adding just one new Kubernetes `CustomResourceDefintion`: `Bundle`. -A `Bundle` represents a set of PEM-encoded X.509 certificates that should be distributed and made -available across the cluster. `Bundle`s are cluster scoped. +A `Bundle` represents a set of X.509 certificates that should be distributed across a cluster. -Users specify a list of `sources`, which trust-manager will query and concatenate certificate data from. -The only other required field is the `target`, which describes how and where the resulting bundle will -be written. +All `Bundle`s are cluster scoped. + +`Bundle`s comprise a list of `sources` from which trust-manager will assemble the final bundle, along with +a `target` describing how and where the resulting bundle will be written. An example `Bundle` might look like this: @@ -67,10 +67,15 @@ spec: 0V3NCaQrXoh+3xrXgX/vMdijYLUSo/YPEWmo -----END CERTIFICATE----- target: - # Data synced to the ConfigMap `my-org.com` at the key `root-certs.pem` in - # every namespace that has the label "linkerd.io/inject=enabled". + # Sync the bundle to a ConfigMap called `my-org.com` in every namespace which + # has the label "linkerd.io/inject=enabled" + # All ConfigMaps will include a PEM-formatted bundle, here named "root-certs.pem" + # and in this case we also request a binary JKS formatted bundle, here named "bundle.jks" configMap: key: "root-certs.pem" + additionalFormats: + jks: + key: "bundle.jks" namespaceSelector: matchLabels: linkerd.io/inject: "enabled" @@ -86,6 +91,16 @@ spec: These sources, along with the single currently supported target type (`configMap`) are documented in the trust-manager [API reference documentation](./api-reference.md). +#### Targets + +All `Bundle` targets are written to `ConfigMap`s whose name matches that of the `Bundle`, and every +target has a PEM-formatted bundle included. + +Users can also optionally - as of trust-manager v0.5.0 - choose to write a JKS formatted binary +bundle to the target. We understand that most Java applications tend to require a password on JKS +files (even though trust bundles don't contain secrets), so all trust-manager JKS bundles use the +default password `changeit`. + #### Namespace Selector A target's `namespaceSelector` is used to restrict which Namespaces your `Bundle`'s target diff --git a/content/docs/projects/trust-manager/api-reference.md b/content/docs/projects/trust-manager/api-reference.md index 994e70ce3b..44ea6f6d18 100644 --- a/content/docs/projects/trust-manager/api-reference.md +++ b/content/docs/projects/trust-manager/api-reference.md @@ -226,6 +226,13 @@ Target is the target location in all namespaces to sync source data to. + additionalFormats + object + + AdditionalFormats specifies any additional formats to write to the target
+ + false + configMap object @@ -243,6 +250,56 @@ Target is the target location in all namespaces to sync source data to. +### `Bundle.spec.target.additionalFormats` + + +AdditionalFormats specifies any additional formats to write to the target + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
jksobject + KeySelector is a reference to a key for some map data object.
+
false
+ + +### `Bundle.spec.target.additionalFormats.jks` + + +KeySelector is a reference to a key for some map data object. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + Key is the key of the entry in the object's `data` field to be used.
+
true
+ + ### `Bundle.spec.target.configMap` @@ -411,6 +468,13 @@ Target is the current Target that the Bundle is attempting or has completed sync + additionalFormats + object + + AdditionalFormats specifies any additional formats to write to the target
+ + false + configMap object @@ -428,6 +492,56 @@ Target is the current Target that the Bundle is attempting or has completed sync +### `Bundle.status.target.additionalFormats` + + +AdditionalFormats specifies any additional formats to write to the target + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
jksobject + KeySelector is a reference to a key for some map data object.
+
false
+ + +### `Bundle.status.target.additionalFormats.jks` + + +KeySelector is a reference to a key for some map data object. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + Key is the key of the entry in the object's `data` field to be used.
+
true
+ + ### `Bundle.status.target.configMap` From 9292a335111d0c0982adbd45721bc448af8ee91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 22 May 2023 15:48:08 +0200 Subject: [PATCH 047/264] add documentation on how to write a good release note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- .../docs/contributing/contributing-flow.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/content/docs/contributing/contributing-flow.md b/content/docs/contributing/contributing-flow.md index 4706a0d169..bd5dff0669 100644 --- a/content/docs/contributing/contributing-flow.md +++ b/content/docs/contributing/contributing-flow.md @@ -73,6 +73,46 @@ To let people know that your PR is still a work in progress, we usually add a `WIP:` prefix to the title of the PR. Prow will then automatically set the label `do-not-merge/work-in-progress`. +### Release Note Guidelines + +The `release-note` code block in the PR description aims at explaining to the +end-user whether they should care about this change and whether they will be +affected. It is OK to have multiple sentences as long as it is written in good +English (subject verb complement, ends with a dot). + +A release note block shouldn't just paraphrase the commit message. For example, +the end-user doesn't know what "comparisons" are and which custom resources are +affected: + +~~~markdown +```release-note +Adds missing comparisons for certain fields which were incorrectly skipped if a LiteralSubject was set +``` +~~~ + +A better release note block gives context and specifically tells the end-user +how they will be affected: + +~~~markdown +```release-note +When using the `literalSubject` on a Certificate, the IPs, URIs, DNS names, and email addresses subject segments are now properly compared. +``` +~~~ + +New lines in the release note block translate to hard line breaks, which means +it is not possible to soft-wrap the release note. A new line can be useful for +lists: + +~~~markdown +```release-note +cainjector: +- New flags were added to the cainjector binary. They can be used to modify what injectable kinds are enabled. If cainjector is only used as a cert-manager's internal component it is sufficient to only enable validatingwebhookconfigurations and mutatingwebhookconfigurations injectable resources; disabling the rest can improve memory consumption. By default all are enabled. +- The `--watch-certs` flag was renamed to `--enable-certificates-data-source`. +``` +~~~ + +If this PR introduces a breaking change, the release note block must start with +`**BREAKING:**` (note the bold text). ### Cherry Picking From b218ae59eba28c8ed3d2f4cf1a8b7f2f87afbae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 19 May 2023 15:33:40 +0200 Subject: [PATCH 048/264] post-release 1.12: update supported releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref: https://github.com/cert-manager/cert-manager/issues/6083 Signed-off-by: Maël Valais --- .../docs/installation/supported-releases.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 8e60cca1f5..6fc4859a75 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -17,18 +17,18 @@ cert-manager expects that ServerSideApply is enabled in the cluster for all vers

Currently supported releases

-| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:------------:|:---------------:|:----------------------------------:|:---------------------------------:| -| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | -| [1.10][] | Oct 17, 2022 | Release of 1.12 | 1.20 → 1.26 | 4.7 → 4.13 | +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| +| [1.12][] | May 19, 2024 | End of September, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | +| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | \*ServerSideApply should be enabled in the cluster ## Upcoming releases -| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:--------------:|:------------------:|:-----------------------------------:|:---------------------------------:| -| [1.12][] | Apr 26, 2023 | End of August, 2023 | 1.22 → 1.26 | 4.9 → 4.13 | +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:----------------------:|:---------------------:|:----------------------------------:|:---------------------------------:| +| [1.13][] | End of September, 2024 | End of November, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | Dates in the future are uncertain and might change. @@ -36,6 +36,7 @@ Dates in the future are uncertain and might change. | Release | Release Date | EOL | Compatible Kubernetes versions | Compatible OpenShift versions | |----------|:------------:|:------------:|:------------------------------:|:-----------------------------:| +| [1.10][] | Oct 17, 2022 | May 19, 2024 | 1.20 → 1.26 | 4.7 → 4.13 | | [1.9][] | Jul 22, 2022 | Jan 11, 2023 | 1.20 → 1.24 | 4.7 → 4.11 | | [1.8][] | Apr 05, 2022 | Oct 17, 2022 | 1.19 → 1.24 | 4.6 → 4.11 | | [1.7][] | Jan 26, 2021 | Jul 22, 2022 | 1.18 → 1.23 | 4.5 → 4.9 | @@ -54,7 +55,8 @@ Dates in the future are uncertain and might change. | [0.11][] | Oct 10, 2019 | Jan 21, 2020 | 1.9 → 1.21 | 3.09 → 4.7 | [s]: #kubernetes-supported-versions -[1.12]: https://github.com/cert-manager/cert-manager/milestone/33 +[1.13]: https://github.com/cert-manager/cert-manager/milestone/34 +[1.12]: https://cert-manager.io/docs/release-notes/release-notes-1.12 [1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 [1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 [1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 @@ -233,6 +235,7 @@ For convenience, the following table shows these version mappings: | OpenShift versions | Kubernetes version | |--------------------|--------------------| +| 4.14 | 1.27 | | 4.13 | 1.26 | | 4.12 | 1.25 | | 4.11 | 1.24 | From 8c4aeb6b9448288079f2c186050a06921a0589a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 19 May 2023 17:53:53 +0200 Subject: [PATCH 049/264] post-release 1.12: "How we determine supported Kubernetes" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/installation/supported-releases.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 6fc4859a75..930152f250 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -212,14 +212,14 @@ In practice, this is largely determined based on what versions of [kind](https:/ are available for testing, and which versions of Kubernetes are provided by major upstream cloud Kubernetes vendors including EKS, GKE, AKS and OpenShift. -| Vendor | Oldest Kubernetes Release\* | Other Older Kubernetes Releases | -|:-----------------:|------------------------------|---------------------------------------------------------------| -| [EKS][eks] | 1.21 (EOL Feb 2023) | 1.22 (EOL May 2023) | -| [GKE][gke] | 1.21 (EOL Feb 2023) | 1.22 (EOL May 2023) | -| [AKS][aks] | 1.23 (EOL ~Feb 2023) | | -| [OpenShift 4][os] | 1.21 (4.8 EUS, EOL Feb 2023) | 1.22 (4.9, EOL Apr 2023) | - -\*Oldest release relevant to the next cert-manager release, as of 2022-07-18 +| Vendor | Oldest Kubernetes Release\* | Other Older Kubernetes Releases | +|:-----------------:|-----------------------------|------------------------------------------------------------------------------------| +| [EKS][eks] | 1.22 (EOL Jun 2023) | 1.23 (EOL Oct 2023), 1.24 (EOL Jan 2024), 1.25 (EOL May 2024), 1.26 (EOL Jun 2024) | +| [GKE][gke] | 1.23 (EOL Jul 2023) | 1.24 (EOL Oct 2023), 1.25 (EOL Feb 2024), 1.26 (EOL May 2024), 1.27 (EOL Jan 2025) | +| [AKS][aks] | 1.24 (EOL Jul 2023) | 1.25 (EOL Dec 2023), 1.26 (EOL Mar 2024), 1.27 (EOL Jun 2024) | +| [OpenShift 4][os] | 1.22 (4.9, EOL Jun 2023) | 1.23 (4.10, EOL Oct 2023), 1.24 (4.11, EOL Feb 2024), 1.25 (4.12, EOL Jan 2025) | + +\*Oldest release relevant to the next cert-manager release, as of 2023-05-19 [eks]: https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html#kubernetes-release-calendar [gke]: https://cloud.google.com/kubernetes-engine/docs/release-schedule From 67087fed1e70ce3e2f23445c98a02179b5ae9f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 19 May 2023 18:19:07 +0200 Subject: [PATCH 050/264] post-release 1.12: bump 1.12 in generate-new-import-path-docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- scripts/gendocs/generate-new-import-path-docs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/gendocs/generate-new-import-path-docs b/scripts/gendocs/generate-new-import-path-docs index b2289b119c..fc5a97361b 100755 --- a/scripts/gendocs/generate-new-import-path-docs +++ b/scripts/gendocs/generate-new-import-path-docs @@ -148,13 +148,13 @@ EOF # This script is _only_ for generating docs for versions of cert-manager with the # github.com/cert-manager/cert-manager import path! -LATEST_VERSION="v1.11-docs" +LATEST_VERSION="v1.12-docs" #genversionwithcli "release-1.8" "v1.8-docs" #genversionwithcli "release-1.9" "v1.9-docs" #genversionwithcli "release-1.10" "v1.10-docs" -genversionwithcli "release-1.11" "$LATEST_VERSION" +genversionwithcli "release-1.12" "$LATEST_VERSION" # Rather than generate the same docs again for /docs, copy from the latest version From afae87f172132aa63147ffa5c1d30799c433955f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 19 May 2023 18:20:27 +0200 Subject: [PATCH 051/264] post-release 1.12: freeze docs/ into v1.12-docs/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command: cp -r content/docs content/v1.12-docs rm -rf content/v1.12-docs/{installation/supported-releases,installation/upgrading,release-notes} Signed-off-by: Maël Valais --- content/v1.12-docs/README.md | 26 + content/v1.12-docs/cli/README.md | 7 + content/v1.12-docs/cli/acmesolver.md | 17 + content/v1.12-docs/cli/cainjector.md | 45 + content/v1.12-docs/cli/cmctl.md | 30 + content/v1.12-docs/cli/controller.md | 78 + content/v1.12-docs/cli/webhook.md | 51 + content/v1.12-docs/concepts/README.md | 11 + .../concepts/acme-orders-challenges.md | 100 + content/v1.12-docs/concepts/ca-injector.md | 234 + content/v1.12-docs/concepts/certificate.md | 106 + .../v1.12-docs/concepts/certificaterequest.md | 261 + content/v1.12-docs/concepts/issuer.md | 44 + content/v1.12-docs/concepts/webhook.md | 80 + content/v1.12-docs/configuration/README.md | 30 + .../v1.12-docs/configuration/acme/README.md | 392 ++ .../configuration/acme/dns01/README.md | 186 + .../configuration/acme/dns01/acme-dns.md | 220 + .../configuration/acme/dns01/akamai.md | 86 + .../configuration/acme/dns01/azuredns.md | 505 ++ .../configuration/acme/dns01/cloudflare.md | 108 + .../configuration/acme/dns01/digitalocean.md | 46 + .../configuration/acme/dns01/google.md | 242 + .../configuration/acme/dns01/rfc2136.md | 207 + .../configuration/acme/dns01/route53.md | 253 + .../configuration/acme/dns01/webhook.md | 32 + .../configuration/acme/http01/README.md | 366 ++ .../acme/http01/externalloadbalancer.md | 34 + content/v1.12-docs/configuration/ca.md | 94 + content/v1.12-docs/configuration/external.md | 51 + .../v1.12-docs/configuration/selfsigned.md | 171 + content/v1.12-docs/configuration/vault.md | 270 + content/v1.12-docs/configuration/venafi.md | 282 + content/v1.12-docs/contributing/README.md | 68 + content/v1.12-docs/contributing/building.md | 267 + .../contributing/coding-conventions.md | 59 + .../contributing/contributing-flow.md | 138 + content/v1.12-docs/contributing/crds.md | 65 + .../v1.12-docs/contributing/dns-providers.md | 23 + content/v1.12-docs/contributing/e2e.md | 148 + .../contributing/external-issuers.md | 77 + .../v1.12-docs/contributing/featuregates.md | 47 + .../google-season-of-docs/2022/README.md | 10 + .../2022/improve-navigation-and-structure.md | 215 + .../google-season-of-docs/README.md | 8 + content/v1.12-docs/contributing/importing.md | 42 + content/v1.12-docs/contributing/kind.md | 31 + content/v1.12-docs/contributing/policy.md | 159 + .../contributing/release-process.md | 599 ++ content/v1.12-docs/contributing/security.md | 13 + content/v1.12-docs/contributing/sign-off.md | 77 + .../v1.12-docs/contributing/signing-keys.md | 72 + .../contributing/third-party-code-donation.md | 79 + content/v1.12-docs/faq/README.md | 182 + content/v1.12-docs/getting-started/README.md | 36 + content/v1.12-docs/installation/README.md | 41 + .../installation/api-compatibility.md | 22 + .../v1.12-docs/installation/best-practice.md | 48 + .../v1.12-docs/installation/code-signing.md | 59 + .../v1.12-docs/installation/compatibility.md | 114 + .../v1.12-docs/installation/featureflags.md | 72 + content/v1.12-docs/installation/helm.md | 207 + content/v1.12-docs/installation/kubectl.md | 124 + .../operator-lifecycle-manager.md | 243 + .../v1.12-docs/installation/other-tools.md | 23 + .../installation/supported-releases.md | 289 + content/v1.12-docs/installation/uninstall.md | 14 + content/v1.12-docs/installation/verify.md | 122 + content/v1.12-docs/manifest.json | 758 +++ content/v1.12-docs/projects/README.md | 31 + .../projects/approver-policy/README.md | 422 ++ .../projects/approver-policy/api-reference.md | 978 +++ .../v1.12-docs/projects/csi-driver-spiffe.md | 257 + content/v1.12-docs/projects/csi-driver.md | 231 + content/v1.12-docs/projects/istio-csr.md | 92 + .../projects/trust-manager/README.md | 373 ++ .../projects/trust-manager/api-reference.md | 478 ++ content/v1.12-docs/reference/README.md | 15 + content/v1.12-docs/reference/api-docs.md | 5674 +++++++++++++++++ content/v1.12-docs/reference/cmctl.md | 344 + .../v1.12-docs/reference/tls-terminology.md | 79 + content/v1.12-docs/troubleshooting/README.md | 116 + content/v1.12-docs/troubleshooting/acme.md | 226 + content/v1.12-docs/troubleshooting/webhook.md | 1071 ++++ content/v1.12-docs/tutorials/README.md | 38 + .../tutorials/acme/dns-validation.md | 169 + .../tutorials/acme/example/deployment.yaml | 20 + .../acme/example/ingress-tls-final.yaml | 24 + .../tutorials/acme/example/ingress-tls.yaml | 24 + .../tutorials/acme/example/ingress.yaml | 24 + .../acme/example/pomerium-certificates.yaml | 36 + .../example/pomerium-production-issuer.yaml | 19 + .../acme/example/pomerium-staging-issuer.yaml | 19 + .../acme/example/pomerium-values.yaml | 39 + .../acme/example/production-issuer.yaml | 18 + .../tutorials/acme/example/service.yaml | 11 + .../acme/example/staging-issuer.yaml | 18 + .../tutorials/acme/http-validation.md | 159 + .../acme/migrating-from-kube-lego.md | 232 + .../tutorials/acme/nginx-ingress.md | 602 ++ .../tutorials/acme/pomerium-ingress.md | 191 + content/v1.12-docs/tutorials/backup.md | 167 + .../getting-started-aks-letsencrypt/README.md | 687 ++ .../README.md | 779 +++ .../example/example-cluster-issuer.yaml | 46 + .../istio-csr/example/example-issuer.yaml | 45 + .../example/istio-config-getting-started.yaml | 57 + .../tutorials/istio-csr/istio-csr.md | 276 + .../syncing-secrets-across-namespaces.md | 143 + content/v1.12-docs/tutorials/venafi/venafi.md | 586 ++ .../v1.12-docs/tutorials/zerossl/zerossl.md | 180 + content/v1.12-docs/usage/README.md | 31 + content/v1.12-docs/usage/approver-policy.md | 14 + content/v1.12-docs/usage/certificate.md | 368 ++ content/v1.12-docs/usage/csi.md | 85 + content/v1.12-docs/usage/gateway.md | 402 ++ content/v1.12-docs/usage/ingress.md | 175 + content/v1.12-docs/usage/istio.md | 17 + content/v1.12-docs/usage/kube-csr.md | 166 + .../v1.12-docs/usage/prometheus-metrics.md | 69 + 120 files changed, 25539 insertions(+) create mode 100644 content/v1.12-docs/README.md create mode 100644 content/v1.12-docs/cli/README.md create mode 100644 content/v1.12-docs/cli/acmesolver.md create mode 100644 content/v1.12-docs/cli/cainjector.md create mode 100644 content/v1.12-docs/cli/cmctl.md create mode 100644 content/v1.12-docs/cli/controller.md create mode 100644 content/v1.12-docs/cli/webhook.md create mode 100644 content/v1.12-docs/concepts/README.md create mode 100644 content/v1.12-docs/concepts/acme-orders-challenges.md create mode 100644 content/v1.12-docs/concepts/ca-injector.md create mode 100644 content/v1.12-docs/concepts/certificate.md create mode 100644 content/v1.12-docs/concepts/certificaterequest.md create mode 100644 content/v1.12-docs/concepts/issuer.md create mode 100644 content/v1.12-docs/concepts/webhook.md create mode 100644 content/v1.12-docs/configuration/README.md create mode 100644 content/v1.12-docs/configuration/acme/README.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/README.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/acme-dns.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/akamai.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/azuredns.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/cloudflare.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/digitalocean.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/google.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/rfc2136.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/route53.md create mode 100644 content/v1.12-docs/configuration/acme/dns01/webhook.md create mode 100644 content/v1.12-docs/configuration/acme/http01/README.md create mode 100644 content/v1.12-docs/configuration/acme/http01/externalloadbalancer.md create mode 100644 content/v1.12-docs/configuration/ca.md create mode 100644 content/v1.12-docs/configuration/external.md create mode 100644 content/v1.12-docs/configuration/selfsigned.md create mode 100644 content/v1.12-docs/configuration/vault.md create mode 100644 content/v1.12-docs/configuration/venafi.md create mode 100644 content/v1.12-docs/contributing/README.md create mode 100644 content/v1.12-docs/contributing/building.md create mode 100644 content/v1.12-docs/contributing/coding-conventions.md create mode 100644 content/v1.12-docs/contributing/contributing-flow.md create mode 100644 content/v1.12-docs/contributing/crds.md create mode 100644 content/v1.12-docs/contributing/dns-providers.md create mode 100644 content/v1.12-docs/contributing/e2e.md create mode 100644 content/v1.12-docs/contributing/external-issuers.md create mode 100644 content/v1.12-docs/contributing/featuregates.md create mode 100644 content/v1.12-docs/contributing/google-season-of-docs/2022/README.md create mode 100644 content/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md create mode 100644 content/v1.12-docs/contributing/google-season-of-docs/README.md create mode 100644 content/v1.12-docs/contributing/importing.md create mode 100644 content/v1.12-docs/contributing/kind.md create mode 100644 content/v1.12-docs/contributing/policy.md create mode 100644 content/v1.12-docs/contributing/release-process.md create mode 100644 content/v1.12-docs/contributing/security.md create mode 100644 content/v1.12-docs/contributing/sign-off.md create mode 100644 content/v1.12-docs/contributing/signing-keys.md create mode 100644 content/v1.12-docs/contributing/third-party-code-donation.md create mode 100644 content/v1.12-docs/faq/README.md create mode 100644 content/v1.12-docs/getting-started/README.md create mode 100644 content/v1.12-docs/installation/README.md create mode 100644 content/v1.12-docs/installation/api-compatibility.md create mode 100644 content/v1.12-docs/installation/best-practice.md create mode 100644 content/v1.12-docs/installation/code-signing.md create mode 100644 content/v1.12-docs/installation/compatibility.md create mode 100644 content/v1.12-docs/installation/featureflags.md create mode 100644 content/v1.12-docs/installation/helm.md create mode 100644 content/v1.12-docs/installation/kubectl.md create mode 100644 content/v1.12-docs/installation/operator-lifecycle-manager.md create mode 100644 content/v1.12-docs/installation/other-tools.md create mode 100644 content/v1.12-docs/installation/supported-releases.md create mode 100644 content/v1.12-docs/installation/uninstall.md create mode 100644 content/v1.12-docs/installation/verify.md create mode 100644 content/v1.12-docs/manifest.json create mode 100644 content/v1.12-docs/projects/README.md create mode 100644 content/v1.12-docs/projects/approver-policy/README.md create mode 100644 content/v1.12-docs/projects/approver-policy/api-reference.md create mode 100644 content/v1.12-docs/projects/csi-driver-spiffe.md create mode 100644 content/v1.12-docs/projects/csi-driver.md create mode 100644 content/v1.12-docs/projects/istio-csr.md create mode 100644 content/v1.12-docs/projects/trust-manager/README.md create mode 100644 content/v1.12-docs/projects/trust-manager/api-reference.md create mode 100644 content/v1.12-docs/reference/README.md create mode 100644 content/v1.12-docs/reference/api-docs.md create mode 100644 content/v1.12-docs/reference/cmctl.md create mode 100644 content/v1.12-docs/reference/tls-terminology.md create mode 100644 content/v1.12-docs/troubleshooting/README.md create mode 100644 content/v1.12-docs/troubleshooting/acme.md create mode 100644 content/v1.12-docs/troubleshooting/webhook.md create mode 100644 content/v1.12-docs/tutorials/README.md create mode 100644 content/v1.12-docs/tutorials/acme/dns-validation.md create mode 100644 content/v1.12-docs/tutorials/acme/example/deployment.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/ingress-tls-final.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/ingress-tls.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/ingress.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/pomerium-certificates.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/pomerium-production-issuer.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/pomerium-staging-issuer.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/pomerium-values.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/production-issuer.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/service.yaml create mode 100644 content/v1.12-docs/tutorials/acme/example/staging-issuer.yaml create mode 100644 content/v1.12-docs/tutorials/acme/http-validation.md create mode 100644 content/v1.12-docs/tutorials/acme/migrating-from-kube-lego.md create mode 100644 content/v1.12-docs/tutorials/acme/nginx-ingress.md create mode 100644 content/v1.12-docs/tutorials/acme/pomerium-ingress.md create mode 100644 content/v1.12-docs/tutorials/backup.md create mode 100644 content/v1.12-docs/tutorials/getting-started-aks-letsencrypt/README.md create mode 100644 content/v1.12-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md create mode 100644 content/v1.12-docs/tutorials/istio-csr/example/example-cluster-issuer.yaml create mode 100644 content/v1.12-docs/tutorials/istio-csr/example/example-issuer.yaml create mode 100644 content/v1.12-docs/tutorials/istio-csr/example/istio-config-getting-started.yaml create mode 100644 content/v1.12-docs/tutorials/istio-csr/istio-csr.md create mode 100644 content/v1.12-docs/tutorials/syncing-secrets-across-namespaces.md create mode 100644 content/v1.12-docs/tutorials/venafi/venafi.md create mode 100644 content/v1.12-docs/tutorials/zerossl/zerossl.md create mode 100644 content/v1.12-docs/usage/README.md create mode 100644 content/v1.12-docs/usage/approver-policy.md create mode 100644 content/v1.12-docs/usage/certificate.md create mode 100644 content/v1.12-docs/usage/csi.md create mode 100644 content/v1.12-docs/usage/gateway.md create mode 100644 content/v1.12-docs/usage/ingress.md create mode 100644 content/v1.12-docs/usage/istio.md create mode 100644 content/v1.12-docs/usage/kube-csr.md create mode 100644 content/v1.12-docs/usage/prometheus-metrics.md diff --git a/content/v1.12-docs/README.md b/content/v1.12-docs/README.md new file mode 100644 index 0000000000..c53f1bc99f --- /dev/null +++ b/content/v1.12-docs/README.md @@ -0,0 +1,26 @@ +--- +title: cert-manager +description: cert-manager documentation homepage +--- + +cert-manager adds certificates and certificate issuers as resource types in +Kubernetes clusters, and simplifies the process of obtaining, renewing and +using those certificates. + +It can issue certificates from a variety of supported sources, including +[Let's Encrypt](https://letsencrypt.org), [HashiCorp Vault](https://www.vaultproject.io), +and [Venafi](https://www.venafi.com/) as well as private PKI. + +It will ensure certificates are valid and up to date, and attempt to +renew certificates at a configured time before expiry. + +It is loosely based upon the work of +[kube-lego](https://github.com/jetstack/kube-lego) and has borrowed some +wisdom from other similar projects such as +[kube-cert-manager](https://github.com/PalmStoneGames/kube-cert-manager). + +![High level overview diagram explaining cert-manager architecture](/images/high-level-overview.svg) + +This website provides the full technical documentation for the project, and can be +used as a reference; if you feel that there's anything missing, please let us know +or [raise a PR](https://github.com/cert-manager/website/pulls) to add it. diff --git a/content/v1.12-docs/cli/README.md b/content/v1.12-docs/cli/README.md new file mode 100644 index 0000000000..0d1a516335 --- /dev/null +++ b/content/v1.12-docs/cli/README.md @@ -0,0 +1,7 @@ +--- +title: CLI reference +description: cert-manager CLI documentation +--- + +View the `--help` output from our various CLI tools, including those which run in containers in your cluster. +This might help if you need to tweak an option or if you need to check which values are valid! \ No newline at end of file diff --git a/content/v1.12-docs/cli/acmesolver.md b/content/v1.12-docs/cli/acmesolver.md new file mode 100644 index 0000000000..baee31aff4 --- /dev/null +++ b/content/v1.12-docs/cli/acmesolver.md @@ -0,0 +1,17 @@ +--- +title: acmesolver CLI reference +description: "cert-manager acmesolver CLI documentation" +--- +``` +HTTP server used to solve ACME challenges. + +Usage: + acmesolver [flags] + +Flags: + --domain string the domain name to verify + -h, --help help for acmesolver + --key string the challenge key to respond with + --listen-port int the port number to listen on for connections (default 8089) + --token string the challenge token to verify against +``` diff --git a/content/v1.12-docs/cli/cainjector.md b/content/v1.12-docs/cli/cainjector.md new file mode 100644 index 0000000000..0bcdf50014 --- /dev/null +++ b/content/v1.12-docs/cli/cainjector.md @@ -0,0 +1,45 @@ +--- +title: cainjector CLI reference +description: "cert-manager cainjector CLI documentation" +--- +``` + +cert-manager CA injector is a Kubernetes addon to automate the injection of CA data into +webhooks and APIServices from cert-manager certificates. + +It will ensure that annotated webhooks and API services always have the correct +CA data from the referenced certificates, which can then be used to serve API +servers and webhook servers. + +Usage: + ca-injector [flags] + +Flags: + --add_dir_header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --enable-profiling Enable profiling for cainjector + --feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are: + AllAlpha=true|false (ALPHA - default=false) + AllBeta=true|false (BETA - default=false) + -h, --help help for ca-injector + --kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster. + --leader-elect If true, cainjector will perform leader election between instances to ensure no more than one instance of cainjector operates at a time (default true) + --leader-election-lease-duration duration The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled. (default 1m0s) + --leader-election-namespace string Namespace used to perform leader election. Only used if leader election is enabled (default "kube-system") + --leader-election-renew-deadline duration The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled. (default 40s) + --leader-election-retry-period duration The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled. (default 15s) + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log_file string If non-empty, use this log file (no effect when -logtostderr=true) + --log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --namespace string If set, this limits the scope of cainjector to a single namespace. If set, cainjector will not update resources with certificates outside of the configured namespace. + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --profiler-address string Address of the Go profiler (pprof) if enabled. This should never be exposed on a public interface. (default "localhost:6060") + --skip_headers If true, avoid header prefixes in the log messages + --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` diff --git a/content/v1.12-docs/cli/cmctl.md b/content/v1.12-docs/cli/cmctl.md new file mode 100644 index 0000000000..dff83b3bc2 --- /dev/null +++ b/content/v1.12-docs/cli/cmctl.md @@ -0,0 +1,30 @@ +--- +title: cmctl CLI reference +description: "cert-manager cmctl CLI documentation" +--- +``` + +cmctl is a CLI tool manage and configure cert-manager resources for Kubernetes + +Usage: cmctl [command] + +Available Commands: + approve Approve a CertificateRequest + check Check cert-manager components + convert Convert cert-manager config files between different API versions + create Create cert-manager resources + deny Deny a CertificateRequest + experimental Interact with experimental features + help Help about any command + inspect Get details on certificate related resources + renew Mark a Certificate for manual renewal + status Get details on current status of cert-manager resources + upgrade Tools that assist in upgrading cert-manager + version Print the cert-manager CLI version and the deployed cert-manager version + +Flags: + -h, --help help for cmctl + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + +Use "cmctl [command] --help" for more information about a command. +``` diff --git a/content/v1.12-docs/cli/controller.md b/content/v1.12-docs/cli/controller.md new file mode 100644 index 0000000000..768ffb05cc --- /dev/null +++ b/content/v1.12-docs/cli/controller.md @@ -0,0 +1,78 @@ +--- +title: controller CLI reference +description: "cert-manager controller CLI documentation" +--- +``` + +cert-manager is a Kubernetes addon to automate the management and issuance of +TLS certificates from various issuing sources. + +It will ensure certificates are valid and up to date periodically, and attempt +to renew certificates at an appropriate time before expiry. + +Usage: + cert-manager-controller [flags] + +Flags: + --acme-http01-solver-image string The docker image to use to solve ACME HTTP01 challenges. You most likely will not need to change this parameter unless you are testing a new feature or developing cert-manager. (default "quay.io/jetstack/cert-manager-acmesolver:canary") + --acme-http01-solver-nameservers strings A list of comma separated dns server endpoints used for ACME HTTP01 check requests. This should be a list containing host and port, for example 8.8.8.8:53,8.8.4.4:53 + --acme-http01-solver-resource-limits-cpu string Defines the resource limits CPU size when spawning new ACME HTTP01 challenge solver pods. (default "100m") + --acme-http01-solver-resource-limits-memory string Defines the resource limits Memory size when spawning new ACME HTTP01 challenge solver pods. (default "64Mi") + --acme-http01-solver-resource-request-cpu string Defines the resource request CPU size when spawning new ACME HTTP01 challenge solver pods. (default "10m") + --acme-http01-solver-resource-request-memory string Defines the resource request Memory size when spawning new ACME HTTP01 challenge solver pods. (default "64Mi") + --acme-http01-solver-run-as-non-root Defines the ability to run the http01 solver as root for troubleshooting issues (default true) + --add_dir_header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --auto-certificate-annotations strings The annotation consumed by the ingress-shim controller to indicate a ingress is requesting a certificate (default [kubernetes.io/tls-acme]) + --cluster-issuer-ambient-credentials Whether a cluster-issuer may make use of ambient credentials for issuers. 'Ambient Credentials' are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the ClusterIssuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata. (default true) + --cluster-resource-namespace string Namespace to store resources owned by cluster scoped resources such as ClusterIssuer in. This must be specified if ClusterIssuers are enabled. (default "kube-system") + --controllers strings A list of controllers to enable. '--controllers=*' enables all on-by-default controllers, '--controllers=foo' enables just the controller named 'foo', '--controllers=*,-foo' disables the controller named 'foo'. + All controllers: issuers, clusterissuers, certificates-metrics, ingress-shim, gateway-shim, orders, challenges, certificaterequests-issuer-acme, certificaterequests-approver, certificaterequests-issuer-ca, certificaterequests-issuer-selfsigned, certificaterequests-issuer-vault, certificaterequests-issuer-venafi, certificates-trigger, certificates-issuing, certificates-key-manager, certificates-request-manager, certificates-readiness, certificates-revision-manager (default [*]) + --copied-annotation-prefixes strings Specify which annotations should/shouldn't be copiedfrom Certificate to CertificateRequest and Order, as well as from CertificateSigningRequest to Order, by passing a list of annotation key prefixes.A prefix starting with a dash(-) specifies an annotation that shouldn't be copied. Example: '*,-kubectl.kuberenetes.io/'- all annotationswill be copied apart from the ones where the key is prefixed with 'kubectl.kubernetes.io/'. (default [*,-kubectl.kubernetes.io/,-fluxcd.io/,-argocd.argoproj.io/]) + --default-issuer-group string Group of the Issuer to use when the tls is requested but issuer group is not specified on the ingress resource. (default "cert-manager.io") + --default-issuer-kind string Kind of the Issuer to use when the tls is requested but issuer kind is not specified on the ingress resource. (default "Issuer") + --default-issuer-name string Name of the Issuer to use when the tls is requested but issuer name is not specified on the ingress resource. + --dns01-check-retry-period duration The duration the controller should wait between a propagation check. Despite the name, this flag is used to configure the wait period for both DNS01 and HTTP01 challenge propagation checks. For DNS01 challenges the propagation check verifies that a TXT record with the challenge token has been created. For HTTP01 challenges the propagation check verifies that the challenge token is served at the challenge URL.This should be a valid duration string, for example 180s or 1h (default 10s) + --dns01-recursive-nameservers strings A list of comma separated dns server endpoints used for DNS01 check requests. This should be a list containing host and port, for example 8.8.8.8:53,8.8.4.4:53 + --dns01-recursive-nameservers-only When true, cert-manager will only ever query the configured DNS resolvers to perform the ACME DNS01 self check. This is useful in DNS constrained environments, where access to authoritative nameservers is restricted. Enabling this option could cause the DNS01 self check to take longer due to caching performed by the recursive nameservers. + --enable-certificate-owner-ref Whether to set the certificate resource as an owner of secret where the tls certificate is stored. When this flag is enabled, the secret will be automatically removed when the certificate resource is deleted. + --enable-profiling Enable profiling for controller. + --feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are: + AdditionalCertificateOutputFormats=true|false (ALPHA - default=false) + AllAlpha=true|false (ALPHA - default=false) + AllBeta=true|false (BETA - default=false) + ExperimentalCertificateSigningRequestControllers=true|false (ALPHA - default=false) + ExperimentalGatewayAPISupport=true|false (ALPHA - default=false) + LiteralCertificateSubject=true|false (ALPHA - default=false) + ServerSideApply=true|false (ALPHA - default=false) + StableCertificateRequestName=true|false (ALPHA - default=false) + UseCertificateRequestBasicConstraints=true|false (ALPHA - default=false) + ValidateCAA=true|false (ALPHA - default=false) + -h, --help help for cert-manager-controller + --issuer-ambient-credentials Whether an issuer may make use of ambient credentials. 'Ambient Credentials' are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the Issuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata. + --kube-api-burst int the maximum burst queries-per-second of requests sent to the Kubernetes apiserver (default 50) + --kube-api-qps float32 indicates the maximum queries-per-second requests to the Kubernetes apiserver (default 20) + --kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster. + --leader-elect If true, cert-manager will perform leader election between instances to ensure no more than one instance of cert-manager operates at a time (default true) + --leader-election-lease-duration duration The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled. (default 1m0s) + --leader-election-namespace string Namespace used to perform leader election. Only used if leader election is enabled (default "kube-system") + --leader-election-renew-deadline duration The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled. (default 40s) + --leader-election-retry-period duration The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled. (default 15s) + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log_file string If non-empty, use this log file (no effect when -logtostderr=true) + --log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --master string Optional apiserver host address to connect to. If not specified, autoconfiguration will be attempted. + --max-concurrent-challenges int The maximum number of challenges that can be scheduled as 'processing' at once. (default 60) + --metrics-listen-address string The host and port that the metrics endpoint should listen on. (default "0.0.0.0:9402") + --namespace string If set, this limits the scope of cert-manager to a single namespace and ClusterIssuers are disabled. If not specified, all namespaces will be watched + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --profiler-address string The host and port that Go profiler should listen on, i.e localhost:6060. Ensure that profiler is not exposed on a public address. Profiler will be served at /debug/pprof. (default "localhost:6060") + --skip_headers If true, avoid header prefixes in the log messages + --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` diff --git a/content/v1.12-docs/cli/webhook.md b/content/v1.12-docs/cli/webhook.md new file mode 100644 index 0000000000..f4d6ef30c3 --- /dev/null +++ b/content/v1.12-docs/cli/webhook.md @@ -0,0 +1,51 @@ +--- +title: webhook CLI reference +description: "cert-manager webhook CLI documentation" +--- +``` +Webhook component providing API validation, mutation and conversion functionality for cert-manager (canary) () + +Usage: + webhook [flags] + +Flags: + --add-dir-header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --api-server-host string Optional apiserver host address to connect to. If not specified, autoconfiguration will be attempted. + --config string Path to a file containing a WebhookConfiguration object used to configure the webhook + --dynamic-serving-ca-secret-name string name of the secret used to store the CA that signs serving certificates certificates + --dynamic-serving-ca-secret-namespace string namespace of the secret used to store the CA that signs serving certificates + --dynamic-serving-dns-names strings DNS names that should be present on certificates generated by the dynamic serving CA + --enable-profiling Enable profiling for webhook. + --feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are: + AdditionalCertificateOutputFormats=true|false (ALPHA - default=false) + AllAlpha=true|false (ALPHA - default=false) + AllBeta=true|false (BETA - default=false) + ExperimentalCertificateSigningRequestControllers=true|false (ALPHA - default=false) + ExperimentalGatewayAPISupport=true|false (ALPHA - default=false) + LiteralCertificateSubject=true|false (ALPHA - default=false) + ServerSideApply=true|false (ALPHA - default=false) + StableCertificateRequestName=true|false (ALPHA - default=false) + UseCertificateRequestBasicConstraints=true|false (ALPHA - default=false) + ValidateCAA=true|false (ALPHA - default=false) + --healthz-port int port number to listen on for insecure healthz connections (default 6080) + -h, --help help for webhook + --kubeconfig string optional path to the kubeconfig used to connect to the apiserver. If not specified, in-cluster-config will be used + --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log-dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log-file string If non-empty, use this log file (no effect when -logtostderr=true) + --log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --profiler-address string Address of the Go profiler (pprof). This should never be exposed on a public interface. If this flag is not set, the profiler is not run. (default "localhost:6060") + --secure-port int port number to listen on for secure TLS connections (default 6443) + --skip-headers If true, avoid header prefixes in the log messages + --skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + --tls-cert-file string path to the file containing the TLS certificate to serve with + --tls-cipher-suites strings Comma-separated list of cipher suites for the server. If omitted, the default Go cipher suites will be use. Possible values: TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_RC4_128_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_RC4_128_SHA + --tls-min-version string Minimum TLS version supported. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13 + --tls-private-key-file string path to the file containing the TLS private key to serve with + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` diff --git a/content/v1.12-docs/concepts/README.md b/content/v1.12-docs/concepts/README.md new file mode 100644 index 0000000000..48ed7e2377 --- /dev/null +++ b/content/v1.12-docs/concepts/README.md @@ -0,0 +1,11 @@ +--- +title: Concepts +description: cert-manager core concepts +--- + +There are several components and ideas that make up cert-manager. This section +describes them on a conceptual level, to aid with understanding how cert-manager +does its job. + +You probably don't want this section if you're just getting started; check out +a [tutorial](../tutorials/README.md) instead. \ No newline at end of file diff --git a/content/v1.12-docs/concepts/acme-orders-challenges.md b/content/v1.12-docs/concepts/acme-orders-challenges.md new file mode 100644 index 0000000000..80c31606e4 --- /dev/null +++ b/content/v1.12-docs/concepts/acme-orders-challenges.md @@ -0,0 +1,100 @@ +--- +title: ACME Orders and Challenges +description: 'cert-manager core concepts: ACME Orders and Challenges' +--- + +cert-manager supports requesting certificates from ACME servers, including from +[Let's Encrypt](https://letsencrypt.org/), with use of the [ACME +Issuer](../configuration/acme/README.md). These certificates are typically trusted on +the public Internet by most computers. To successfully request a certificate, +cert-manager must solve ACME Challenges which are completed in order to prove +that the client owns the DNS addresses that are being requested. + +In order to complete these challenges, cert-manager introduces two +`CustomResource` types; `Orders` and `Challenges`. + +## Orders + +`Order` resources are used by the ACME issuer to manage the lifecycle of an ACME +'order' for a signed TLS certificate. More details on ACME orders and domain +validation can be found on the Let's Encrypt website +[here](https://letsencrypt.org/how-it-works/). An order represents a single +certificate request which will be created automatically once a new +[`CertificateRequest`](./certificaterequest.md) resource referencing an ACME +issuer has been created. `CertificateRequest` resources are created +automatically by cert-manager once a [`Certificate`](./certificate.md) resource +is created, has its specification changed, or needs renewal. + +As an end-user, you will never need to manually create an `Order` resource. +Once created, an `Order` cannot be changed. Instead, a new `Order` resource must +be created. + +The `Order` resource encapsulates multiple ACME 'challenges' for that 'order', +and as such, will manage one or more `Challenge` resources. + +## Challenges + +`Challenge` resources are used by the ACME issuer to manage the lifecycle of an +ACME 'challenge' that must be completed in order to complete an 'authorization' +for a single DNS name/identifier. + +When an `Order` resource is created, the order controller will create +`Challenge` resources for each DNS name that is being authorized with the ACME +server. + +As an end-user, you will never need to manually create a `Challenge` resource. +Once created, a `Challenge` cannot be changed. Instead, a new `Challenge` +resource must be created. + +### Challenge Lifecycle + +After a `Challenge` resource has been created, it will be initially queued for +processing. Processing will not begin until the challenge has been 'scheduled' +to start. This scheduling process prevents too many challenges being attempted +at once, or multiple challenges for the same DNS name being attempted at once. +For more information on how challenges are scheduled, read the [challenge +scheduling](#challenge-scheduling). + +Once a challenge has been scheduled, it will first be 'synced' with the ACME +server in order to determine its current state. If the challenge is already +valid, its 'state' will be updated to 'valid', and will also set +`status.processing = false` to 'unschedule' itself. + +If the challenge is still 'pending', the challenge controller will 'present' the +challenge using the configured solver, one of HTTP01 or DNS01. Once the +challenge has been 'presented', it will set `status.presented = true`. + +Once 'presented', the challenge controller will perform a 'self check' to +ensure that the challenge has 'propagated' (i.e. the authoritative DNS servers +have been updated to respond correctly, or the changes to the ingress resources +have been observed and in-use by the ingress controller). + +If the self check fails, cert-manager will retry the self check with a fixed 10 +second retry interval. Challenges that do not ever complete the self check will +continue retrying until the user intervenes by either retrying the `Order` (by +deleting the `Order` resource) or amending the associated `Certificate` resource +to resolve any configuration errors. + +Once the self check is passing, the ACME 'authorization' associated with this +challenge will be 'accepted'. + +The final state of the authorization after accepting it will be copied across to +the Challenge's `status.state` field, as well as the 'error reason' if an error +occurred whilst the ACME server attempted to validate the challenge. + +Once a Challenge has entered the `valid`, `invalid`, `expired` or `revoked` +state, it will set `status.processing = false` to prevent any further processing +of the ACME challenge, and to allow another challenge to be scheduled if there +is a backlog of challenges to complete. + +### Challenge Scheduling + +Instead of attempting to process all challenges at once, challenges are +'scheduled' by cert-manager. + +This scheduler applies a cap on the maximum number of simultaneous challenges +as well as disallows two challenges for the same DNS name and solver type +(`HTTP01` or `DNS01`) to be completed at once. + +The maximum number of challenges that can be processed at a time is 60 as of +[`ddff78`](https://github.com/cert-manager/cert-manager/blob/ddff78f011558e64186d61f7c693edced1496afa/pkg/controller/acmechallenges/scheduler/scheduler.go#L31-L33). \ No newline at end of file diff --git a/content/v1.12-docs/concepts/ca-injector.md b/content/v1.12-docs/concepts/ca-injector.md new file mode 100644 index 0000000000..2c7c8dd638 --- /dev/null +++ b/content/v1.12-docs/concepts/ca-injector.md @@ -0,0 +1,234 @@ +--- +title: CA Injector +description: 'cert-manager core concepts: CA Injector' +--- + +`cainjector` helps to configure the CA certificates for: +[Mutating Webhooks], +[Validating Webhooks] +[Conversion Webhooks] and [API Services] + +In particular, `cainjector` populates the `caBundle` field of four API types: +`ValidatingWebhookConfiguration`, +`MutatingWebhookConfiguration` +`CustomResourceDefinition` and `APIService`. +The first three resource types are used to configure how the Kubernetes API server connects to webhooks. +This `caBundle` data is loaded by the Kubernetes API server and used to verify the serving certificates of webhook API servers. +`APIService` is used to represent an [Extension API Server]. `caBundle` of `APIService` can be populated with CA cert that can be used to validate the API server's serving certificate. + +We will refer to these four API types as *injectable* resources. + + +An *injectable* resource MUST have one of these annotations: +`cert-manager.io/inject-ca-from`, +`cert-manager.io/inject-ca-from-secret`, or +`cert-manager.io/inject-apiserver-ca`, depending on the injection *source*. +This is explained in more detail below. + +`cainjector` copies CA data from one of three *sources*: +a Kubernetes `Secret`, +a cert-manager `Certificate`, or from +the Kubernetes API server CA certificate (which `cainjector` itself uses to verify its TLS connection to the Kubernetes API server). + +If the *source* is a Kubernetes `Secret`, that resource MUST also have an `cert-manager.io/allow-direct-injection: "true"` annotation. +The three *source* types are explained in more detail below. + + +## Examples + +Here are examples demonstrating how to use the three `cainjector` *sources*. +In each case we use `ValidatingWebhookConfiguration` as the *injectable*, +but you can substitute `MutatingWebhookConfiguration` or `CustomResourceDefinition` definition instead. + +### Injecting CA data from a Certificate resource + +Here is an example of a `ValidatingWebhookConfiguration` +configured with the annotation `cert-manager.io/inject-ca-from`, +which will make `cainjector` populate the `caBundle` field using CA data from a cert-manager `Certificate`. + +NOTE: This example does not deploy a webhook server, +it only deploys a partial webhook configuration, +but it should be sufficient to help you understand what `cainjector` does: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: example1 + +--- + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: webhook1 + annotations: + cert-manager.io/inject-ca-from: example1/webhook1-certificate +webhooks: +- name: webhook1.example.com + admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook1 + namespace: example1 + path: /validate + port: 443 + sideEffects: None + +--- + +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: webhook1-certificate + namespace: example1 +spec: + secretName: webhook1-certificate + dnsNames: + - webhook1.example1 + issuerRef: + name: selfsigned + +--- + +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned + namespace: example1 +spec: + selfSigned: {} +``` + +You should find that the `caBundle` value is now identical to the CA value in the `Secret` for the `Certificate`: + +``` +kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io webhook1 -o yaml | grep caBundle +kubectl -n example1 get secret webhook1-certificate -o yaml | grep ca.crt +``` + +And after a short time, the Kubernetes API server will read that new `caBundle` value and use it to verify a TLS connection to the webhook server. + +### Injecting CA data from a Secret resource + +Here is another example of a `ValidatingWebhookConfiguration` +this time configured with the annotation `cert-manager.io/inject-ca-from-secret`, +which will make `cainjector` populate the `caBundle` field using CA data from a Kubernetes `Secret`. + +NOTE: This example does not deploy a webhook server, +it only deploys a partial webhook configuration, +but it should be sufficient to help you understand what `cainjector` does: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: example2 + +--- + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: webhook2 + annotations: + cert-manager.io/inject-ca-from-secret: example2/example-ca +webhooks: +- name: webhook2.example.com + admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook2 + namespace: example2 + path: /validate + port: 443 + sideEffects: None + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: example-ca + namespace: example2 + annotations: + cert-manager.io/allow-direct-injection: "true" +type: kubernetes.io/tls +data: + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5akNDQWQ2Z0F3SUJBZ0lRTkdJZ24yM3BQYVpNbk9MUjJnVmZHakFOQmdrcWhraUc5dzBCQVFzRkFEQVYKTVJNd0VRWURWUVFERXdwRmVHRnRjR3hsSUVOQk1CNFhEVEl3TURreU5ERTFOREEwTVZvWERUSXdNVEl5TXpFMQpOREEwTVZvd0ZURVRNQkVHQTFVRUF4TUtSWGhoYlhCc1pTQkRRVENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFECmdnRVBBRENDQVFvQ2dnRUJBS2F3RzVoMzlreHdyNEl0WCtHaDNYVWQrdTVJc2ZlSFdoTTc4TTRQTmZFeXhQMXoKRmNLN1d0MHJFMkwwNUppYmQ4ZjNpb3k5OXNnQ3I4OEw2SWxYZTB0RnkzNysxenJ4TFluR2hDQnZzZjltd0hLbgpIVTEvNERwQjROZkhPbFllNE9tbHVoNE9HdmZINU1EbDh5OWZGMjhXRXVBQ2dwdmpCUWxvRDNlVjJ5UmJvQ2kyCmtSTDJWYTFZL0FQZEpWK21VYkFvZmg0bllmUmNLRTJsSUg0RG5ZdXFPU3JaaituZUQ2M2RTSktxcHQ5K2luN2YKNHljZ2pQYU93MmdyKzhLK291QTlSQTV1VDI3SVNJcUJDcEV6elRqbVBUUWNvUTYxZGF0aDZkc1lsTEU4aWZWUwp4RWZuVEdQKy94M0FXQXR4eU5lanVuZGFXbVNFL3h5OHh0K0FxblVDQXdFQUFhTkNNRUF3RGdZRFZSMFBBUUgvCkJBUURBZ0trTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkowNkc5eEc2V1VBTHB6T3JYaHAKV2dsTm5qMkFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUI3ZG9CZnBLR3o4VlRQSnc0YXhpdisybzJpMHE1SQpSRzU2UE81WnhKQktZQlRROElHQmFOSm1yeGtmNTJCV0ttUGp4cXlNSGRwWjVBU00zOUJkZVUzRGtEWHp4RkgwCjM5RU12UnhIUERyMGQ4cTFFbndQT0xZY1hzNjJhYjdidE11cTJUMFNNZzRYMkY5VmNKTW5YdjlrNnA0VGZNR3MKVThCQnJhVGhUZm53ejBsWXMyblFjdzNmZjZ1bG1wWlk4K3BTak1aVDNJZHZOMFA4Y2hOdUlmUFRHWDJmSlo2NQpxcUUrelRoU3hIeXFTOTVoczhsd1lRRUhGQlVsalRnMCtQZThXL0hOSXZBOU9TYWw1U3UvdlhydmcxN04xdHVyCk5XcWRyZU5OVm1ubXMvTFJodmthWTBGblRvbFNBRkNXWS9GSDY5ZzRPcThiMHVyK3JVMHZOZFFXCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: "" + tls.crt: "" +``` + +You should find that the `caBundle` value is now identical to the `ca.crt` value in the `Secret`: + +``` +kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io webhook2 -o yaml | grep caBundle +``` + +And after a short time, the Kubernetes API server will read that new `caBundle` value and use it to verify a TLS connection to the webhook server. + +This `Secret` based injection mechanism can operate independently of the `Certificate` based mechanism described earlier. +It will work without the cert-manager CRDs installed +and it will work if the cert-manager CRDs and associated webhook servers are not yet configured. + +NOTE: For this reason, cert-manager uses the `Secret` based injection mechanism to bootstrap its own webhook server. +The cert-manager webhook server generates its own private key and self-signed certificate and places them in a `Secret` when it starts up. + +### Injecting the Kubernetes API Server CA + +Here is another example of a `ValidatingWebhookConfiguration` +this time configured with the annotation `cert-manager.io/inject-apiserver-ca: "true"`, +which will make `cainjector` populate the `caBundle` field using the same CA certificate used by the Kubernetes API server. + +NOTE: This example does not deploy a webhook server, +it only deploys a partial webhook configuration, +but it should be sufficient to help you understand what `cainjector` does: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: example3 + +--- + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: webhook3 + annotations: + cert-manager.io/inject-apiserver-ca: "true" +webhooks: +- name: webhook3.example.com + admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook3 + namespace: example3 + path: /validate + port: 443 + sideEffects: None + +``` + +You should find that the `caBundle` value is now identical to the CA used in your `KubeConfig` file: + +``` +kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io webhook3 -o yaml | grep caBundle +kubectl config view --minify --raw | grep certificate-authority-data +``` + +And after a short time, the Kubernetes API server will read that new `caBundle` value and use it to verify a TLS connection to the webhook server. + +NOTE: In this case you will have to ensure that your webhook is configured to serve a TLS certificate that has been signed by the Kubernetes cluster CA. +The disadvantages of this mechanism are that: you will require access to the private key of the Kubernetes cluster CA and you will need to manually rotate the webhook certificate. + +[Validating Webhooks]: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook +[Mutating Webhooks]: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook +[Conversion Webhooks]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion +[API Services]: https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/api-service-v1/ +[Extension API Server]: https://kubernetes.io/docs/tasks/extend-kubernetes/setup-extension-api-server/ \ No newline at end of file diff --git a/content/v1.12-docs/concepts/certificate.md b/content/v1.12-docs/concepts/certificate.md new file mode 100644 index 0000000000..3d8ca12198 --- /dev/null +++ b/content/v1.12-docs/concepts/certificate.md @@ -0,0 +1,106 @@ +--- +title: Certificate +description: 'cert-manager core concepts: Certificates' +--- + +cert-manager has the concept of `Certificates` that define a desired X.509 +certificate which will be renewed and kept up to date. A `Certificate` is a +namespaced resource that references an `Issuer` or `ClusterIssuer` that +determine what will be honoring the certificate request. + +When a `Certificate` is created, a corresponding `CertificateRequest` resource +is created by cert-manager containing the encoded X.509 certificate request, +`Issuer` reference, and other options based upon the specification of the +`Certificate` resource. + +Here is one such example of a `Certificate` resource. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: acme-crt +spec: + secretName: acme-crt-secret + dnsNames: + - example.com + - foo.example.com + issuerRef: + name: letsencrypt-prod + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: Issuer + group: cert-manager.io +``` + +This `Certificate` will tell cert-manager to attempt to use the `Issuer` named +`letsencrypt-prod` to obtain a certificate key pair for the `example.com` and +`foo.example.com` domains. If successful, the resulting TLS key and certificate +will be stored in a secret named `acme-crt-secret`, with keys of `tls.key`, and +`tls.crt` respectively. This secret will live in the same namespace as the +`Certificate` resource. + +When a certificate is issued by an intermediate CA and the `Issuer` can provide +the issued certificate's chain, the contents of `tls.crt` will be the requested +certificate followed by the certificate chain. + +Additionally, if the Certificate Authority is known, the corresponding CA +certificate will be stored in the secret with key `ca.crt`. For example, with +the ACME issuer, the CA is not known and `ca.crt` will not exist in +`acme-crt-secret`. + +cert-manager intentionally avoids adding root certificates to `tls.crt`, because they +are useless in a situation where TLS is being done securely. For more information, +see [RFC 5246 section 7.4.2](https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.2) +which contains the following explanation: + +> Because certificate validation requires that root keys be distributed +> independently, the self-signed certificate that specifies the root +> certificate authority MAY be omitted from the chain, under the +> assumption that the remote end must already possess it in order to +> validate it in any case. + +
+ +When configuring a client to connect to a TLS server with a serving certificate that is signed by a private CA, +you will need to provide the client with the CA certificate in order for it to verify the server. +`ca.crt` will likely contain the certificate you need to trust, +but __do not mount the same `Secret` as the server__ to access `ca.crt`. +This is because: + +1. That `Secret` also contains the private key of the server, which should only be accessible to the server. + You should use RBAC to ensure that the `Secret` containing the serving certificate and private key are only accessible to Pods that need it. +2. Rotating CA certificates safely relies on being able to have both the old and new CA certificates trusted at the same time. + By consuming the CA directly from the source, this isn't possible; + you'll be _forced_ to have some down-time in order to rotate certificates. + +When configuring the client you should independently choose and fetch the CA certificates that you want to trust. +Download the CA out of band and store it in a `Secret` or `ConfigMap` separate from the `Secret` containing the server's private key and certificate. + +This ensures that if the material in the `Secret` containing the server key and certificate is tampered with, +the client will fail to connect to the compromised server. + +The same concept also applies when configuring a server for mutually-authenticated TLS; +don't give the server access to Secret containing the client certificate and private key. + +
+ +The `dnsNames` field specifies a list of [`Subject Alternative +Names`](https://en.wikipedia.org/wiki/Subject_Alternative_Name) to be associated +with the certificate. + +The referenced `Issuer` must exist in the same namespace as the `Certificate`. +A `Certificate` can alternatively reference a `ClusterIssuer` which is +non-namespaced and so can be referenced from any namespace. + +You can read more on how to configure your `Certificate` resources +[here](../usage/certificate.md). + +## Certificate Lifecycle + +This diagram shows the lifecycle of a Certificate named `cert-1` using an +ACME / Let's Encrypt issuer. You don't need to understand all of these steps +to use cert-manager; this is more of an explanation of the logic which happens +under the hood for those curious about the process. + +![Life of a Certificate](/images/letsencrypt-flow-cert-manager.png) \ No newline at end of file diff --git a/content/v1.12-docs/concepts/certificaterequest.md b/content/v1.12-docs/concepts/certificaterequest.md new file mode 100644 index 0000000000..5dbc1ca546 --- /dev/null +++ b/content/v1.12-docs/concepts/certificaterequest.md @@ -0,0 +1,261 @@ +--- +title: CertificateRequest +description: 'cert-manager core concepts: CertificateRequests' +--- + +The `CertificateRequest` is a namespaced resource in cert-manager that is used +to request X.509 certificates from an [`Issuer`](./issuer.md). The resource +contains a base64 encoded string of a PEM encoded certificate request which is +sent to the referenced issuer. A successful issuance will return a signed +certificate, based on the certificate signing request. `CertificateRequests` are +typically consumed and managed by controllers or other systems and should not be +used by humans - unless specifically needed. + +A simple `CertificateRequest` looks like the following: + +```yaml +apiVersion: cert-manager.io/v1 +kind: CertificateRequest +metadata: + name: my-ca-cr +spec: + request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQzNqQ0NBY1lDQVFBd2daZ3hDekFKQmdOVkJBWVRBbHBhTVE4d0RRWURWUVFJREFaQmNHOXNiRzh4RFRBTApCZ05WQkFjTUJFMXZiMjR4RVRBUEJnTlZCQW9NQ0VwbGRITjBZV05yTVJVd0V3WURWUVFMREF4alpYSjBMVzFoCmJtRm5aWEl4RVRBUEJnTlZCQU1NQ0dwdmMyaDJZVzVzTVN3d0tnWUpLb1pJaHZjTkFRa0JGaDFxYjNOb2RXRXUKZG1GdWJHVmxkWGRsYmtCcVpYUnpkR0ZqYXk1cGJ6Q0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQwpBUW9DZ2dFQkFLd01tTFhuQkNiRStZdTIvMlFtRGsxalRWQ3BvbHU3TlZmQlVFUWl1bDhFMHI2NFBLcDRZQ0c5Cmx2N2kwOHdFMEdJQUgydnJRQmxVd3p6ZW1SUWZ4YmQvYVNybzRHNUFBYTJsY2NMaFpqUlh2NEVMaER0aVg4N3IKaTQ0MWJ2Y01OM0ZPTlRuczJhRkJYcllLWGxpNG4rc0RzTEVuZmpWdXRiV01Zeis3M3ptaGZzclRJUjRzTXo3cQpmSzM2WFM4UkRjNW5oVVcyYU9BZ3lnbFZSOVVXRkxXNjNXYXVhcHg2QUpBR1RoZnJYdVVHZXlZUUVBSENxZmZmCjhyOEt3YTFYK1NwYm9YK1ppSVE0Nk5jQ043OFZnL2dQVHNLZmphZURoNWcyNlk1dEVidHd3MWdRbWlhK0MyRHIKWHpYNU13RzJGNHN0cG5kUnRQckZrU1VnMW1zd0xuc0NBd0VBQWFBQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQgpBUUFXR0JuRnhaZ0gzd0N3TG5IQ0xjb0l5RHJrMUVvYkRjN3BJK1VVWEJIS2JBWk9IWEFhaGJ5RFFLL2RuTHN3CjJkZ0J3bmlJR3kxNElwQlNxaDBJUE03eHk5WjI4VW9oR3piN0FVakRJWHlNdmkvYTJyTVhjWjI1d1NVQmxGc28Kd005dE1QU2JwcEVvRERsa3NsOUIwT1BPdkFyQ0NKNnZGaU1UbS9wMUJIUWJSOExNQW53U0lUYVVNSFByRzJVMgpjTjEvRGNMWjZ2enEyeENjYVoxemh2bzBpY1VIUm9UWmV1ZEp6MkxmR0VHM1VOb2ppbXpBNUZHd0RhS3BySWp3ClVkd1JmZWZ1T29MT1dNVnFNbGRBcTlyT24wNHJaT3Jnak1HSE9tTWxleVdPS1AySllhaDNrVDdKU01zTHhYcFYKV0ExQjRsLzFFQkhWeGlKQi9Zby9JQWVsCi0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo= + isCA: false + usages: + - signing + - digital signature + - server auth + # 90 days + duration: 2160h + issuerRef: + name: ca-issuer + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: Issuer + group: cert-manager.io +``` + +This `CertificateRequest` will make cert-manager attempt to request the `Issuer` +`ca-issuer` in the default issuer group `cert-manager.io`, return a +certificate based upon the certificate signing request. Other groups can be +specified inside the `issuerRef` which will change the targeted issuer to other +external, third party issuers you may have installed. + +The resource also exposes the option for stating the certificate as CA, Key +Usages, and requested validity duration. + +All fields within the `spec` of the `CertificateRequest`, as well as any managed +cert-manager annotations, are immutable and cannot be modified after creation. + +A successful issuance of the certificate signing request will cause an update to +the resource, setting the status with the signed certificate, the CA of the +certificate (if available), and setting the `Ready` condition to `True`. + +Whether issuance of the certificate signing request was successful or not, a retry of the +issuance will _not_ happen. It is the responsibility of some other controller to +manage the logic and life cycle of `CertificateRequests`. + +## Conditions +`CertificateRequests` have a set of strongly defined conditions that should be +used and relied upon by controllers or services to make decisions on what +actions to take next on the resource. + +### Ready +Each ready condition consists of the pair `Ready` - a boolean value, and +`Reason` - a string. The set of values and meanings are as follows: + +| Ready | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| False | Pending | The `CertificateRequest` is currently pending, waiting for some other operation to take place. This could be that the `Issuer` does not exist yet or the `Issuer` is in the process of issuing a certificate. | +| False | Failed | The certificate has failed to be issued - either the returned certificate failed to be decoded or an instance of the referenced issuer used for signing failed. No further action will be taken on the `CertificateRequest` by its controller and it can be considered terminally failed. | +| True | Issued | A signed certificate has been successfully issued by the referenced `Issuer`. | + +This condition should be set by the issuer. + +### Denied +| Denied | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| True | \ | The `CertificateRequest` was Denied by an approver. This `CertificateRequest` can be considered terminally failed. + +This condition should only be set by an approver. + +### Approved +| Approved | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| True | \ | The `CertificateRequest` was approved by the approver. This `CertificateRequest` is approved and can be issued by the issuer. + +This condition should only be set by an approver. + +### InvalidRequest +| InvalidRequest | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| True | \ | The `CertificateRequest` is invalid. This `CertificateRequest` can be considered terminally failed. + + +## UserInfo + +`CertificateRequests` include a set of `UserInfo` fields as part of the spec, +namely: `username`, `groups`, `uid`, and `extra`. These values contain the user +who created the `CertificateRequest`. This user will be cert-manager itself in +the case that the `CertificateRequest` was created by a +[`Certificate`](./certificate.md) resource, or instead the user who created the +`CertificateRequest` directly. + +> **Warning**: These fields are managed by cert-manager and should _never_ be +> set or modified by anything else. When the `CertificateRequest` is created, +> these fields will be overridden, and any request attempting to modify them +> will be rejected. + + +### Approval +CertificateRequests can be `Approved` or `Denied`. These mutually exclusive +conditions gate a CertificateRequest from being signed by its managed signer. + +- A signer should _not_ sign a managed CertificateRequest without an Approved condition +- A signer _will_ sign a managed CertificateRequest with an Approved condition +- A signer will _never_ sign a managed CertificateRequest with a Denied condition + +These conditions are _permanent_, and cannot be modified or changed once set. + +```bash +NAMESPACE NAME APPROVED DENIED READY ISSUER REQUESTOR AGE +istio-system service-mesh-ca-whh5b True True mesh-ca system:serviceaccount:istio-system:istiod 16s +istio-system my-app-fj9sa True mesh-ca system:serviceaccount:my-app:my-app 4s +``` + + +#### Behavior + +The Approved and Denied conditions are two distinct condition types on the +CertificateRequest. These conditions must only have the status of True, and +are mutually exclusive (i.e. a CertificateRequest cannot have an Approved and +Denied condition simultaneously). This behavior is enforced in the cert-manager +validating admission webhook. + +An "approver" is an entity that is responsible for setting the Approved/Denied +conditions. It is up to the approver's implementation as to what +CertificateRequests are managed by that approver. + +The Reason field of the Approved/Denied condition should be set to *who* set the +condition. Who can be interpreted however makes sense to the approver +implementation. For example, it may include the API group of an approving policy +controller, or the client agent of a manual request. + +The Message field of the Approved/Denied condition should be set to *why* the +condition is set. Again, why can be interpreted however makes sense to the +implementation of the approver. For example, the name of the resource that +approves this request, the violations which caused the request to be denied, or +the team to who manually approved the request. + + +#### Approver Controller + +By default, cert-manager will run an internal approval controller which will +automatically approve _all_ CertificateRequests that reference any internal +issuer type in any namespace: `cert-manager.io/Issuer`, +`cert-manager.io/ClusterIssuer`. + +To disable this controller, add the following argument to the +cert-manager-controller: `--controllers=*,-certificaterequests-approver`. This +can be achieved with helm by appending: + +```bash +--set extraArgs={--controllers='*\,-certificaterequests-approver'} +``` + +Alternatively, in order for the internal approver controller to approve +CertificateRequests that reference an external issuer, add the following RBAC to +the cert-manager-controller Service Account. Please replace the given resource +names with the relevant names: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-approve:my-issuer-example-com # edit +rules: +- apiGroups: + - cert-manager.io + resources: + - signers + verbs: + - approve + resourceNames: + - issuers.my-issuer.example.com/* # edit + - clusterissuers.my-issuer.example.com/* # edit +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-approve:my-issuer-example-com # edit +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-approve:my-issuer-example-com # edit +subjects: +- kind: ServiceAccount + name: cert-manager + namespace: cert-manager +``` + +#### RBAC Syntax + +When a user or controller attempts to approve or deny a CertificateRequest, the +cert-manager webhook will evaluate whether it has sufficient permissions to do +so. These permissions are based upon the request +itself- specifically the request's IssuerRef: + +```yaml +apiGroups: ["cert-manager.io"] +resources: ["signers"] +verbs: ["approve"] +resourceNames: + # namesapced signers + - "./." + # cluster scoped signers + - "./" + # all signers of this resource name + - "./*" +``` + +An example ClusterRole that would grant the permissions to set the Approve and +Denied conditions of CertificateRequests that reference the cluster scoped +`myissuers` external issuer, in the group `my-example.io`, with the name `myapp`: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: my-example-io-my-issuer-myapp-approver +rules: + - apiGroups: ["cert-manager.io"] + resources: ["signers"] + verbs: ["approve"] + resourceNames: ["myissuers.my-example.io/myapp"] +``` + +If the approver does not have sufficient permissions defined above to set the +Approved or Denied conditions, the request will be rejected by the cert-manager +validating admission webhook. + +- The RBAC permissions *must* be granted at the cluster scope +- Namespaced signers are represented by a namespaced resource using the syntax of `./.` +- Cluster scoped signers are represented using the syntax of `./` +- An approver can be granted approval for all namespaces via `./*` +- The apiGroup must *always* be `cert-manager.io` +- The resource must *always* be `signers` +- The verb must *always* be `approve`, which grants the approver the permissions to set *both* Approved and Denied conditions + +An example of signing all `myissuer` signers in all namespaces, and +`clustermyissuers` with the name `myapp`, in the `my-example.io` group: + +```yaml + resourceNames: ["myissuers.my-example.io/*", "clustermyissuers.my-example.io/myapp"] +``` + +An example of signing `myissuer` with the name `myapp` in the namespaces `foo` +and `bar`: + +```yaml + resourceNames: ["myissuers.my-example.io/foo.myapp", "myissuers.my-example.io/bar.myapp"] +``` \ No newline at end of file diff --git a/content/v1.12-docs/concepts/issuer.md b/content/v1.12-docs/concepts/issuer.md new file mode 100644 index 0000000000..6293369992 --- /dev/null +++ b/content/v1.12-docs/concepts/issuer.md @@ -0,0 +1,44 @@ +--- +title: Issuer +description: 'cert-manager core concepts: Issuers and ClusterIssuers' +--- + +`Issuers`, and `ClusterIssuers`, are Kubernetes resources that represent +certificate authorities (CAs) that are able to generate signed certificates by honoring +certificate signing requests. All cert-manager certificates require a referenced +issuer that is in a ready condition to attempt to honor the request. + +An example of an `Issuer` type is `CA`. A simple `CA` `Issuer` is as follows: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ca-issuer + namespace: mesh-system +spec: + ca: + secretName: ca-key-pair +``` + +This is a simple `Issuer` that will sign certificates based on a private key. +The certificate stored in the secret `ca-key-pair` can then be used to trust +newly signed certificates by this `Issuer` in a Public Key Infrastructure (PKI) +system. + +## Namespaces + +An `Issuer` is a namespaced resource, and it is not possible to issue +certificates from an `Issuer` in a different namespace. This means you will need +to create an `Issuer` in each namespace you wish to obtain `Certificates` in. + +If you want to create a single `Issuer` that can be consumed in multiple +namespaces, you should consider creating a `ClusterIssuer` resource. This is +almost identical to the `Issuer` resource, however is non-namespaced so it +can be used to issue `Certificates` across all namespaces. + +## Supported Issuers + +cert-manager supports a number of 'in-tree', as well as 'out-of-tree' `Issuer` +types. An exhaustive list of these `Issuer` types can be found in the +cert-manager [configuration documentation](../configuration/README.md). diff --git a/content/v1.12-docs/concepts/webhook.md b/content/v1.12-docs/concepts/webhook.md new file mode 100644 index 0000000000..1bcbbee769 --- /dev/null +++ b/content/v1.12-docs/concepts/webhook.md @@ -0,0 +1,80 @@ +--- +title: All About the cert-manager Webhook +description: | + Learn about the webhook component of cert-manager, which validates, converts and sets default values for the cert-manager custom resources +--- + +cert-manager extends the Kubernetes API using Custom Resource Definitions. +It installs a webhook which has three main functions: + +- [Validation](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook): + Ensures that when cert-manager resources are created or updated, they conform + to the rules of the API. This validation is more in depth than for example + ensuring resources conform to the OpenAPI schema, but instead contains logic such as + not allowing to specify more than one `Issuer` type per `Issuer` resource. The + validating admission is always called and will respond with a success or + failed response. +- [Mutation / Defaulting](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook): + Changes the contents of resources during create and update operations, for + example to set default values. +- [Conversion](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion): + The webhook is also responsible for implementing a conversion over versions + in the cert-manager `CustomResources` (`cert-manager.io`). This means that + multiple API versions can be supported simultaneously; from `v1alpha2` through to `v1`. + This makes it possible to rely on a particular version of our + configuration schema. + +> ℹ️ This is known as Dynamic Admission Control. +> Read more about [Dynamic Admission Control](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) in the Kubernetes documentation. + +## Overview + +The webhook component is deployed as another pod that runs alongside the main +cert-manager controller and CA injector components. + +In order for the API server to communicate with the webhook component, the +webhook requires a TLS certificate that the apiserver is configured to trust. + +The [`cainjector`](./ca-injector.md) creates `secret/cert-manager-webhook-ca`, a self-signed root CA certificate which is used to sign certificates for the webhook pod. + +Then the webhook can be configured with either + +1. paths to a TLS certificate and key signed by the webhook CA, or +2. a reference to the CA Secret for dynamic generation of the certificate and key on webhook startup + +## Known Problems and Solutions + +### Webhook connection problems on GKE private cluster + +If errors occur around the webhook but the webhook is running then the webhook +is most likely not reachable from the API server. In this case, ensure that the +API server can communicate with the webhook by following the [GKE private +cluster explanation](../installation/compatibility.md#gke). + +### Webhook connection problems on AWS EKS + +When using a custom CNI (such as Weave or Calico) on EKS, the webhook cannot be reached by cert-manager. +This happens because the control plane cannot be configured to run on a custom CNI on EKS, +so the CNIs differ between control plane and worker nodes. +The solution is to [run the webhook in the host network](../installation/compatibility.md#aws-eks) so it can be reached by cert-manager. + +### Webhook connection problems shortly after cert-manager installation + +When you first install cert-manager, it will take a few seconds before the cert-manager API is usable. +This is because the cert-manager API requires the cert-manager webhook server, which takes some time to start up. +Here's why: + +* The webhook server performs a leader election at startup which may take a few seconds. +* The webhook server may take a few seconds to start up and to generate its self-signed CA and serving certificate and to publish those to a Secret. +* `cainjector` performs a leader election at start up which can take a few seconds. +* `cainjector`, once started, will take a few seconds to update the `caBundle` in all the webhook configurations. + +For these reasons, after installing cert-manager and when performing post-installation cert-manager API operations, +you will need to check for temporary API configuration errors and retry. + +You could also add a post-installation check which performs `kubectl --dry-run` operations on the cert-manager API. +Or you could add a post-installation check which automatically retries the [Installation Verification](../installation/verify.md) steps until they succeed. + +### Other Webhook Problems + +If you encounter any other problems with the webhook, please refer to the [webhook troubleshooting guide](../troubleshooting/webhook.md). diff --git a/content/v1.12-docs/configuration/README.md b/content/v1.12-docs/configuration/README.md new file mode 100644 index 0000000000..85c5d1334f --- /dev/null +++ b/content/v1.12-docs/configuration/README.md @@ -0,0 +1,30 @@ +--- +title: Issuer Configuration +description: Learn about configuring cert-manager using Issuer and ClusterIssuer resources. +--- + +The first thing you'll need to configure after you've installed cert-manager is an `Issuer` or a `ClusterIssuer`. +These are resources that represent certificate authorities (CAs) +able to sign certificates in response to certificate signing requests. + +This section documents how the different issuer types can be configured. You might want to +[read more about `Issuer` and `ClusterIssuer` resources](../concepts/issuer.md). + +cert-manager comes with a number of built-in certificate issuers which are denoted by being in +the `cert-manager.io` group. You can also install external issuers in addition to the built-in types. +Built-in and external issuers are treated the same and are configured similarly. + +## Cluster Resource Namespace + +When using `ClusterIssuer` resource types, ensure you understand the purpose of the +Cluster Resource Namespace; this can be a common source +of issues for people getting started with cert-manager. + +The `ClusterIssuer` resource is cluster scoped. This means that when referencing +a secret via the `secretName` field, secrets will be looked for in the `Cluster +Resource Namespace`. By default, this namespace is `cert-manager` however it can be +changed via a flag on the cert-manager-controller component: + +```bash +--cluster-resource-namespace=my-namespace +``` diff --git a/content/v1.12-docs/configuration/acme/README.md b/content/v1.12-docs/configuration/acme/README.md new file mode 100644 index 0000000000..3c658510e0 --- /dev/null +++ b/content/v1.12-docs/configuration/acme/README.md @@ -0,0 +1,392 @@ +--- +title: ACME +description: 'cert-manager configuration: ACME Issuers' +--- + +The ACME Issuer type represents a single account registered with the Automated +Certificate Management Environment (ACME) Certificate Authority server. When you +create a new ACME `Issuer`, cert-manager will generate a private key which is +used to identify you with the ACME server. + +Certificates issued by public ACME servers are typically trusted by client's +computers by default. This means that, for example, visiting a website that is +backed by an ACME certificate issued for that URL, will be trusted by default by +most client's web browsers. ACME certificates are typically free. + +## Solving Challenges + +In order for the ACME CA server to verify that a client owns the domain, or +domains, a certificate is being requested for, the client must complete +"challenges". This is to ensure clients are unable to request certificates for +domains they do not own and as a result, fraudulently impersonate another's +site. As detailed in the [RFC8555](https://tools.ietf.org/html/rfc8555), +cert-manager offers two challenge validations - HTTP01 and DNS01 challenges. + +[HTTP01](./http01/README.md) challenges are completed by presenting a computed +key, that should be present at a HTTP URL endpoint and is routable over the +internet. This URL will use the domain name requested for the certificate. Once +the ACME server is able to get this key from this URL over the internet, the +ACME server can validate you are the owner of this domain. When a HTTP01 +challenge is created, cert-manager will automatically configure your cluster +ingress to route traffic for this URL to a small web server that presents this +key. + +[DNS01](./dns01/README.md) challenges are completed by providing a computed key +that is present at a DNS TXT record. Once this TXT record has been propagated +across the internet, the ACME server can successfully retrieve this key via a +DNS lookup and can validate that the client owns the domain for the requested +certificate. With the correct permissions, cert-manager will automatically +present this TXT record for your given DNS provider. + +## Configuration + +### Creating a Basic ACME Issuer + +All ACME `Issuers` follow a similar configuration structure - a clients `email`, +a `server` URL, a `privateKeySecretRef`, and one or more `solvers`. Below is an +example of a simple ACME issuer: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + # You must replace this email address with your own. + # Let's Encrypt will use this to contact you about expiring + # certificates, and issues related to your account. + email: user@example.com + server: https://acme-staging-v02.api.letsencrypt.org/directory + privateKeySecretRef: + # Secret resource that will be used to store the account's private key. + name: example-issuer-account-key + # Add a single challenge solver, HTTP01 using nginx + solvers: + - http01: + ingress: + class: nginx +``` + +Solvers come in the form of [`dns01`](./dns01/README.md) and +[`http01`](./http01/README.md) stanzas. For more information on how to configure +these solver types, visit their respective documentation - +[DNS01](./dns01/README.md), [HTTP01](./http01/README.md). + +### External Account Bindings + +cert-manager supports using External Account Bindings with your ACME account. +External Account Bindings are used to associate your ACME account with an +external account such as a CA custom database. This is typically not needed for +most cert-manager users unless you know it is explicitly needed. + +External Account Bindings require two fields on an ACME `Issuer` which +represents your ACME account. These fields are: + +- `keyID` - the key ID or account ID of which your external account binding is indexed by the +external account manager +- `keySecretRef` - the name and key of a secret containing a base 64 encoded +URL string of your external account symmetric MAC key + +> Note: In _most_ cases, the MAC key must be encoded in `base64URL`. The +> following command will base64-encode a key and convert it to `base64URL`: +> +> ```console +> $ echo 'my-secret-key' | base64 -w0 | sed -e 's/+/-/g' -e 's/\//_/g' -e 's/=//g' +> ``` +> +> You can then create the Secret resource with: +> +> ```console +> $ kubectl create secret generic eab-secret --from-literal \ +> secret={base64 encoded secret key} +> ``` + +An example of an ACME issuer with an External Account Binding is as follows. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: my-acme-server-with-eab +spec: + acme: + email: user@example.com + server: https://my-acme-server-with-eab.com/directory + externalAccountBinding: + keyID: my-keyID-1 + keySecretRef: + name: eab-secret + key: secret + privateKeySecretRef: + name: example-issuer-account-key + solvers: + - http01: + ingress: + class: nginx +``` +> Note: cert-manager versions pre-`v1.3.0` also required users to specify the +> MAC algorithm for EAB by setting +> `Issuer.spec.acme.externalAccountBinding.keyAlgorithm` field. This field is +> now deprecated because the upstream Go `x/crypto` library hardcodes the algorithm +> to `HS256`. (See related discussion upstream +> [`CL#41430`](https://github.com/golang/go/issues/41430)). +### Reusing an ACME Account + +You may want to reuse a single ACME account across multiple clusters. This +might especially be useful when using EAB. If the `disableAccountKeyGeneration` +field is set, cert-manager will not create a new ACME account and use the +existing key specified in `privateKeySecretRef`. Note that the +`Issuer`/`ClusterIssuer` will not be ready and will continue to retry until the +`Secret` is provided. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: my-acme-server-with-existing-acme-account +spec: + acme: + email: user@example.com + disableAccountKeyGeneration: true + privateKeySecretRef: + name: example-issuer-account-key +``` + + +### Adding Multiple Solver Types + +You may want to use different types of challenge solver configurations for +different ingress controllers, for example if you want to issue wildcard +certificates using `DNS01` alongside other certificates that are validated using +`HTTP01`. + +The `solvers` stanza has an optional `selector` field, that can be used to +specify which `Certificates`, and further, what DNS names *on those* +`Certificates` should be used to solve challenges. + +There are three selector types that can be used to form the requirements that a +`Certificate` must meet in order to be selected for a solver - `matchLabels`, +`dnsNames` and `dnsZones`. You can have any number of these three selectors on a +single solver. + + +#### Match Labels + +The `matchLabel` selector requires that all `Certificates` match all of +the labels that are defined in the string map list of that stanza. For example, +the following `Issuer` will only match on `Certificates` that have the labels +`"user-cloudflare-solver": "true"` and `"email": "user@example.com"`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + matchLabels: + "use-cloudflare-solver": "true" + "email": "user@example.com" +``` + +#### DNS Names + +The `dnsNames` selector is a list of exact DNS names that should be mapped to a +solver. This means that `Certificates` containing any of these DNS names will +be selected. If a match is found, a `dnsNames` selector will take precedence +over a [`dnsZones`](#dns-zones) selector. If multiple solvers match with the +same `dnsNames` value, the solver with the most matching labels in +[`matchLabels`](#match-labels) will be selected. If neither has more matches, +the solver defined earlier in the list will be selected. + +The following example will solve challenges of `Certificates` with DNS names +`example.com` and `*.example.com` for these domains. + +> Note: `dnsNames` take an exact match and do not resolve wildcards, meaning the +> following `Issuer` *will not* solve for DNS names such as `foo.example.com`. +> Use the [`dnsZones`](#dns-zones) selector type to match all subdomains within +> a zone. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + dnsNames: + - 'example.com' + - '*.example.com' +``` + +#### DNS Zones + +The `dnsZones` stanza defines a list of DNS zones that can be solved by this +solver. If a DNS name is an exact match, or a subdomain of any of the specified +`dnsZones`, this solver will be used, unless a more specific +[`dnsNames`](#dns-names) match is configured. This means that `sys.example.com` +will be selected over one specifying `example.com` for the domain +`www.sys.example.com`. If multiple solvers match with the same `dnsZones` value, +the solver with the most matching labels in [`matchLabels`](#match-labels) will +be selected. If neither has more matches, the solver defined earlier in the list +will be selected. + +In the following example, this solver will resolve challenges for the domain +`example.com`, as well as all of its subdomains `*.example.com`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + dnsZones: + - 'example.com' +``` + +#### All Together + +Each solver is able to have any number of the three selector types defined. In +the following example, the `DNS01` solver for CloudFlare will be used to solve +challenges for domains for `Certificates` that contain the DNS names +`a.example.com` and `b.example.com`. The `DNS01` solver for Google CloudDNS will +be used to solve challenges for `Certificates` whose DNS names match +zone `test.example.com` and all of its subdomains (e.g. `foo.test.example.com`). + +For all other challenges, the `HTTP01` solver will be used *only* if the +`Certificate` also contains the label `"use-http01-solver": "true"`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + ... + solvers: + - http01: + ingress: + class: nginx + selector: + matchLabels: + "use-http01-solver": "true" + - dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + dnsNames: + - 'a.example.com' + - 'b.example.com' + - dns01: + cloudDNS: + project: my-project-id + hostedZoneName: 'test-example.com' + serviceAccountSecretRef: + key: sa + name: gcp-sa-secret + selector: + dnsZones: + - 'test.example.com' # This should be the DNS name of the zone +``` + +Each individual selector block can contain more than one selector type for +example: + +```yaml +solvers: +- dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + matchLabels: + 'email': 'user@example.com' + 'solver': 'cloudflare' + dnsZones: + - 'test.example.com' + - 'example.dev' +``` + +In this case the `DNS01` solver for Cloudflare will only be used to solve a +challenge for a DNS name if the `Certificate` has a label from +`matchLabels` _and_ the DNS name matches a zone from `dnsZones`. + +## Private ACME Servers + +cert-manager should also work with private or self-hosted ACME servers, as long as they follow the ACME spec. + +If your ACME server doesn't use a publicly trusted certificate, you can pass a trusted CA to use when creating your +issuer, from cert-manager 1.11 onwards: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: my-acme-server-issuer +spec: + acme: + server: https://my-acme-server.example.com + caBundle: + ... +``` + + +## Alternative Certificate Chains + +{/* The empty link below preserves old links to #alternative-certificate-chain", which matched the old title of this section */} + + + +It's possible to choose alternative certificate chains when fetching a certificate from an ACME server. This allows issuers to gracefully roll people over to a new root certificate during a transition period; the most famous example was the Let's Encrypt ["ISRG Root" changeover](https://community.letsencrypt.org/t/transition-to-isrgs-root-delayed-until-jan-11-2021/125516). + +This functionality is not exclusive to Let's Encrypt; if your ACME server supports signing by multiple CAs you can use `preferredChain` with the value of the Common Name of the chain you want in the Issuer part of the certificate. If the common name matches a difference chain, the server can choose to use and return that new chain. + +If the `preferredChain` does not match a certificate the server will return whatever it considers to be its default certificate. + +By way of an example, below is how a user would have requested an alternative chain before the (now completed) "ISRG Root" changeover, but note that since this change has already happened there's no need for this with Let's Encrypt any more: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + preferredChain: "ISRG Root X1" +``` diff --git a/content/v1.12-docs/configuration/acme/dns01/README.md b/content/v1.12-docs/configuration/acme/dns01/README.md new file mode 100644 index 0000000000..b9240f8045 --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/README.md @@ -0,0 +1,186 @@ +--- +title: DNS01 +description: 'cert-manager configuration: ACME DNS-01 challenges overview' +--- + +## Configuring DNS01 Challenge Provider + +This page contains details on the different options available on the `Issuer` +resource's DNS01 challenge solver configuration. + +For more information on configuring ACME `Issuers` and their API format, read the +[ACME Issuers](../README.md) documentation. + +DNS01 provider configuration must be specified on the `Issuer` resource, similar +to the examples in the setting up documentation. + +You can read about how the DNS01 challenge type works on the [Let's Encrypt +challenge types +page](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge). + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + email: user@example.com + server: https://acme-staging-v02.api.letsencrypt.org/directory + privateKeySecretRef: + name: example-issuer-account-key + solvers: + - dns01: + cloudDNS: + project: my-project + serviceAccountSecretRef: + name: prod-clouddns-svc-acct-secret + key: service-account.json +``` + +Each issuer can specify multiple different DNS01 challenge providers, and +it is also possible to have multiple instances of the same DNS provider on a +single `Issuer` (e.g. two CloudDNS accounts could be set, each with their own +name). + +For more information on utilizing multiple solver types on a single `Issuer`, +read the multiple-solver-types section. + +## Setting Nameservers for DNS01 Self Check + +cert-manager will check the correct DNS records exist before attempting a DNS01 +challenge. By default cert-manager will use the recursive nameservers taken +from `/etc/resolv.conf` to query for the authoritative nameservers, which it will +then query directly to verify the DNS records exist. + +If this is not desired (for example with multiple authoritative nameservers or +split-horizon DNS), the cert-manager controller exposes two flags that allows +you alter this behavior: + +`--dns01-recursive-nameservers` Comma separated string with host and port of the +recursive nameservers cert-manager should query. + +`--dns01-recursive-nameservers-only` Forces cert-manager to only use the +recursive nameservers for verification. Enabling this option could cause the DNS01 +self check to take longer due to caching performed by the recursive nameservers. + + +Example usage: +```bash +--dns01-recursive-nameservers-only --dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53 +``` + +If you're using the `cert-manager` helm chart, you can set recursive nameservers +through `.Values.extraArgs` or at the command at helm install/upgrade time +with `--set`: + +```bash +--set 'extraArgs={--dns01-recursive-nameservers-only,--dns01-recursive-nameservers=8.8.8.8:53\,1.1.1.1:53}' +``` + +## Delegated Domains for DNS01 + +By default, cert-manager will not follow CNAME records pointing to subdomains. + +If granting cert-manager access to the root DNS zone is not desired, then the +`_acme-challenge.example.com` subdomain can instead be delegated to some other, +less privileged domain (`less-privileged.example.org`). This could be achieved in the following way. Say, one has two zones: + +* `example.com` +* `less-privileged.example.org` + +1. Create a CNAME record pointing to this less privileged domain: +``` +_acme-challenge.example.com IN CNAME _acme-challenge.less-privileged.example.org. +``` + +2. Grant cert-manager rights to update less privileged `less-privileged.example.org` zone + +3. Provide configuration/credentials for updating this less privileged zone +and add an additional field into the relevant `dns01` solver. Note that `selector` +field is still working for the original `example.com`, while credentials are provided for +`less-privileged.example.org` + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + ... +spec: + acme: + ... + solvers: + - selector: + dnsZones: + - 'example.com' + dns01: + # Valid values are None and Follow + cnameStrategy: Follow + route53: + region: eu-central-1 + accessKeyID: + hostedZoneID: + secretAccessKeySecretRef: + ... +``` + +If you have a multitude of (sub)domains requiring separate certificates, +it is possible to share an aliased less-privileged domain. To achieve it one should +create a CNAME record for each (sub)domain like this: + +```txt +_acme-challenge.example.com IN CNAME _acme-challenge.less-privileged.example.org. +_acme-challenge.www.example.com IN CNAME _acme-challenge.less-privileged.example.org. +_acme-challenge.foo.example.com IN CNAME _acme-challenge.less-privileged.example.org. +_acme-challenge.bar.example.com IN CNAME _acme-challenge.less-privileged.example.org. +``` + +With this configuration cert-manager will follow CNAME records recursively in order to determine +which DNS zone to update during DNS01 challenges. + + +## Supported DNS01 providers + +A number of different DNS providers are supported for the ACME `Issuer`. Below +is a listing of available providers, their `.yaml` configurations, along with +additional Kubernetes and provider specific notes regarding their usage. + +- [ACMEDNS](./acme-dns.md) +- [Akamai](./akamai.md) +- [AzureDNS](./azuredns.md) +- [CloudFlare](./cloudflare.md) +- [Google](./google.md) +- [Route53](./route53.md) +- [DigitalOcean](./digitalocean.md) +- [RFC2136](./rfc2136.md) + +## Webhook + +cert-manager also supports out of tree DNS providers using an external webhook. +Links to these supported providers along with their documentation are below: + +- [`AliDNS-Webhook`](https://github.com/pragkent/alidns-webhook) +- [`cert-manager-alidns-webhook`](https://github.com/DEVmachine-fr/cert-manager-alidns-webhook) +- [`cert-manager-webhook-civo`](https://github.com/okteto/cert-manager-webhook-civo) +- [`cert-manager-webhook-dnspod`](https://github.com/qqshfox/cert-manager-webhook-dnspod) +- [`cert-manager-webhook-dnsimple`](https://github.com/neoskop/cert-manager-webhook-dnsimple) +- [`cert-manager-webhook-gandi`](https://github.com/bwolf/cert-manager-webhook-gandi) +- [`cert-manager-webhook-infomaniak`](https://github.com/Infomaniak/cert-manager-webhook-infomaniak) +- [`cert-manager-webhook-inwx`](https://gitlab.com/smueller18/cert-manager-webhook-inwx) +- [`cert-manager-webhook-linode`](https://github.com/slicen/cert-manager-webhook-linode) +- [`cert-manager-webhook-oci`](https://gitlab.com/dn13/cert-manager-webhook-oci) (Oracle Cloud Infrastructure) +- [`cert-manager-webhook-scaleway`](https://github.com/scaleway/cert-manager-webhook-scaleway) +- [`cert-manager-webhook-selectel`](https://github.com/selectel/cert-manager-webhook-selectel) +- [`cert-manager-webhook-softlayer`](https://github.com/cgroschupp/cert-manager-webhook-softlayer) +- [`cert-manager-webhook-ibmcis`](https://github.com/jb-dk/cert-manager-webhook-ibmcis) +- [`cert-manager-webhook-loopia`](https://github.com/Identitry/cert-manager-webhook-loopia) +- [`cert-manager-webhook-arvan`](https://github.com/kiandigital/cert-manager-webhook-arvan) +- [`bizflycloud-certmanager-dns-webhook`](https://github.com/bizflycloud/bizflycloud-certmanager-dns-webhook) +- [`cert-manager-webhook-hetzner`](https://github.com/vadimkim/cert-manager-webhook-hetzner) +- [`cert-manager-webhook-yandex-cloud`](https://github.com/malinink/cert-manager-webhook-yandex-cloud) +- [`cert-manager-webhook-netcup`](https://github.com/aellwein/cert-manager-webhook-netcup) +- [`cert-manager-webhook-pdns`](https://github.com/zachomedia/cert-manager-webhook-pdns) + +You can find more information on how to configure webhook providers [here](./webhook.md). + +To create a new unsupported DNS provider, follow the development documentation [here](../../../contributing/dns-providers.md). diff --git a/content/v1.12-docs/configuration/acme/dns01/acme-dns.md b/content/v1.12-docs/configuration/acme/dns01/acme-dns.md new file mode 100644 index 0000000000..968531bf5f --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/acme-dns.md @@ -0,0 +1,220 @@ +--- +title: ACMEDNS +description: 'cert-manager configuration: ACME DNS-01 challenges using ACMEDNS' +--- + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + solvers: + - dns01: + acmeDNS: + host: https://acme.example.com + accountSecretRef: + name: acme-dns + key: acmedns.json +``` + +In general, clients to ACMEDNS perform registration on the users behalf and +inform them of the CNAME entries they must create. This is not possible in +cert-manager, it is a non-interactive system. Registration must be carried out +beforehand and the resulting credentials JSON uploaded to the cluster as a +`Secret`. In this example, we use `curl` and the API endpoints directly. +Information about setting up and configuring ACMEDNS is available on the +[ACMEDNS project page](https://github.com/joohoi/acme-dns). + +1. First, register with the ACMEDNS server, in this example, there is one + running at `auth.example.com`. The command: + + ```sh + curl -X POST http://auth.example.com/register + ``` + + will return a JSON with credentials for your registration: + + ```json + { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": [] + } + ``` + + It is strongly recommended to restrict the update endpoint to the IP + range of your pods. This is done at registration time as follows: + + ```sh + curl -X POST http://auth.example.com/register \ + -H "Content-Type: application/json" \ + --data '{"allowfrom": ["10.244.0.0/16"]}' + ``` + + Make sure to update the `allowfrom` field to match your cluster + configuration. The JSON will now look like: + + ```json + { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + } + ``` + +2. Save this JSON to a file with the key as your domain. You can specify + multiple domains with the same credentials if you like. In our example, + the returned credentials can be used to verify ownership of + `example.com` and and `example.org`. + + ```json + { + "example.com": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + }, + "example.org": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + } + } + ``` + +3. Next, update your primary DNS server with the CNAME record that will tell the + verifier how to locate the challenge TXT record. This is obtained from the + `fulldomain` field in the registration: + + ``` + _acme-challenge.example.com CNAME d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com + _acme-challenge.example.org CNAME d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com + ``` + + The "name" of the record always has the _acme-challenge subdomain, and + the "value" of the record matches exactly the fulldomain field from + registration. + + At verification time, the domain name `d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com` will be a TXT + record that is set to your validation token. When the verifier queries `_acme-challenge.example.com`, it will + be directed to the correct location by this CNAME record. This proves that you control `example.com` + +4. Create a secret from the credentials JSON that was saved in step 2, this + secret is referenced in the `accountSecretRef` field of your DNS01 + issuer settings. When creating an `Issuer` both this `Issuer` and + `Secret` must be in the same namespace. However for a `ClusterIssuer` + (which does not have a namespace) the `Secret` must be placed in the + same namespace as where the cert-manager pod is running in (in the + default setup `cert-manager`). + + ```sh + kubectl create secret generic acme-dns --from-file acmedns.json + ``` + +## Limitation of the `acme-dns` server + +The [`acme-dns`](https://github.com/joohoi/acme-dns) server has a [known +limitation](https://github.com/cert-manager/cert-manager/issues/3610#issuecomment-849792721): +when a set of credentials is used with more than 2 domains, cert-manager +will fail solving the DNS01 challenges. + +Imagining that you have configured the ACMEDNS issuer with a single set of +credentials, and that the "subdomain" of this set of credentials is +`d420c923-bbd7-4056-ab64-c3ca54c9b3cf`: + +```yaml +kind: Secret +metadata: + name: auth-example-com +stringData: + acmedns.json: | + { + "example.com": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + }, + } +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: my-acme-dns +spec: + acme: + solvers: + - dns01: + acmeDNS: + accountSecretRef: + name: auth-example-com + key: acmedns.json + host: auth.example.com +``` + +and imagine that you want to create a Certificate with three subdomains: + +```yaml +kind: Certificate +spec: + issuerRef: + name: issuer-1 + dnsNames: + - "example.com" + - "*.example.com" + - "foo.example.com" +``` + +cert-manager will only be able to solve 2 challenges out of 3 in a non +deterministic way. This limitation comes from a "feature" mentioned [this +acme-dns issue](https://github.com/joohoi/acme-dns/issues/76). + +One workaround is to issue one set of acme-dns credentials for each +domain that we want to be challenged, keeping in mind that each acme-dns +"subdomain" can only accept at most 2 challenged domains. For example, the +above secret would become: + +```yaml +kind: Secret +metadata: + name: auth-example-com +stringData: + acmedns.json: | + { + "example.com": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + }, + "foo.example.com": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + } +``` + +With this setup, we have: + +- `example.com` and `*.example.com` are registered in the acme-dns + "subdomain" `d420c923-bbd7-4056-ab64-c3ca54c9b3cf`. +- `foo.example.com` is registered in the acme-dns "subdomain" + `d420c923-bbd7-4056-ab64-c3ca54c9b3cf`. + +Another workaround is to use `--max-concurrent-challenges 2` when running +the `cert-manager-controller`. With this setting, acme-dns will only have 2 +TXT records in its database at any time, which mitigates the issue. \ No newline at end of file diff --git a/content/v1.12-docs/configuration/acme/dns01/akamai.md b/content/v1.12-docs/configuration/acme/dns01/akamai.md new file mode 100644 index 0000000000..271f7fd620 --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/akamai.md @@ -0,0 +1,86 @@ +--- +title: Akamai +description: 'cert-manager configuration: ACME DNS-01 challenges using Akamai DNS' +--- + +## Edge DNS + +Use Edge DNS to solve DNS01 ACME challenges by creating a `Secret` using [Akamai API credentials](https://developer.akamai.com/getting-started/edgegrid) and an `Issuer` that references the `Secret` and sets the solver type. + +### Create a Secret + +The `Secret` should look like the following for the `Issuer` to reference. Replace `use_akamai_client_secret`, `use_akamai_access_token` and `use_akamai_client_token` with the respective Akamai API credential values. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: akamai-secret +type: Opaque +stringData: + clientSecret: use_akamai_client_secret + accessToken: use_akamai_access_token + clientToken: use_akamai_client_token +``` + +### Create an Issuer + +To set Edge DNS for challenge tokens, `cert-manager` uses an `Issuer` that references the above `Secret` and other attributes such as the solver type. The `Issuer` should look like the following. Replace `use_akamai_host` with the Akamai API credential `host` value. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-akamai-dns +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: contact@me.com + privateKeySecretRef: + name: letsencrypt-akamai-issuer-account-key + solvers: + - dns01: + akamai: + serviceConsumerDomain: use_akamai_host + clientTokenSecretRef: + name: akamai-secret + key: clientToken + clientSecretSecretRef: + name: akamai-secret + key: clientSecret + accessTokenSecretRef: + name: akamai-secret + key: accessToken +``` + +### Create a Certificate + +The `Certificate` should look like the following and reference the Akamai Edge DNS `Issuer` above. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-zone +spec: + secretName: akamai-crt-secret + dnsNames: + - '*.example.zone' + issuerRef: + name: letsencrypt-akamai-dns + kind: Issuer +``` + +> Note: `cert-manager` will wait for challenge tokens to propagate across the Edge DNS network. Follow the `certificate` status with a command such as the following. + +```bash +kubectl describe certificate example-zone +``` + +### Troubleshooting + +Follow the `cert-manager` events to identify any issues with a command such as the following. + +```bash +cmctl status certificate example-zone +``` \ No newline at end of file diff --git a/content/v1.12-docs/configuration/acme/dns01/azuredns.md b/content/v1.12-docs/configuration/acme/dns01/azuredns.md new file mode 100644 index 0000000000..bc5de32fec --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/azuredns.md @@ -0,0 +1,505 @@ +--- +title: AzureDNS +description: 'cert-manager configuration: ACME DNS-01 challenges using AzureDNS' +--- + +cert-manager can create and then delete DNS-01 records in Azure DNS but it needs to authenticate to Azure first. +There are four authentication methods available: + +- [Managed Identity Using AAD Workload Identity](#managed-identity-using-aad-pod-identity) (recommended) +- [Managed Identity Using AAD Pod Identities](#managed-identity-using-aad-pod-identities) (deprecated) +- [Managed Identity Using AKS Kubelet Identity](#managed-identity-using-aks-kubelet-identity) +- [Service Principal](#service-principal) + +## Managed Identity Using AAD Workload Identity + +> ℹ️ This feature is available in cert-manager `>= v1.11.0`. +> +> 📖 Read the [AKS + LoadBalancer + Let's Encrypt tutorial](../../../tutorials/getting-started-aks-letsencrypt/README.md) for an end-to-end example of this authentication method. + +Azure AD workload identity (preview) on Azure Kubernetes Service (AKS) allows cert-manager to authenticate to Azure using a Kubernetes ServiceAccount Token and then to manage DNS-01 records in Azure DNS. +This is the recommended authentication method because it is more secure and easier to maintain than the other methods. + +### Reconfigure the cluster + +Enable the workload identity federation features on your cluster. +If you have an Azure AKS cluster you can use the following command: + +```bash +az aks update \ + --name ${CLUSTER} \ + --enable-oidc-issuer \ + --enable-workload-identity # ℹ️ This option is currently only available when using the aks-preview extension. +``` + +> ℹ️ You can [install the Azure workload identity extension on other managed and self-managed clusters](https://azure.github.io/azure-workload-identity/docs/installation.html) if you are not using Azure AKS. +> +> 📖 Read [Deploy and configure workload identity on an Azure Kubernetes Service (AKS) cluster](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster) for more information about the `--enable-workload-identity` feature. +> +### Reconfigure cert-manager + +Label the cert-manager controller Pod and ServiceAccount for the attention of the Azure Workload Identity webhook, +which will result in the cert-manager controller Pod having an extra volume containing a Kubernetes ServiceAccount token which it will use to authenticate with Azure. + +If you installed cert-manager using Helm, the labels can be configured using Helm values: + +```yaml +# values.yaml +podLabels: + azure.workload.identity/use: "true" +serviceAccount: + labels: + azure.workload.identity/use: "true" +``` + +If successful, the cert-manager Pod will have some new environment variables set, +and the Azure workload-identity ServiceAccount token as a projected volume: + +```bash +kubectl describe pod -n cert-manager -l app.kubernetes.io/component=controller +``` + +```terminal +Containers: + ... + cert-manager-controller: + ... + Environment: + ... + AZURE_CLIENT_ID: + AZURE_TENANT_ID: f99bd6a4-665c-41cf-aff1-87a89d5c62d4 + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + AZURE_AUTHORITY_HOST: https://login.microsoftonline.com/ + Mounts: + /var/run/secrets/azure/tokens from azure-identity-token (ro) +Volumes: + ... + azure-identity-token: + Type: Projected (a volume that contains injected data from multiple sources) + TokenExpirationSeconds: 3600 +``` + +> 📖 Read about [the role of the Mutating Admission Webhook](https://azure.github.io/azure-workload-identity/docs/installation/mutating-admission-webhook.html) in Azure AD Workload Identity for Kubernetes. + + +### Create a Managed Identity + +In order for cert-manager to use the Azure API and manipulate the records in the Azure DNS zone, +it needs an Azure account and the best type of account to use is called a "Managed Identity". +This account does not come with a password or an API key and it is designed for use by machines rather than humans. + +Choose a managed identity name and create the Managed Identity: + +```bash +export IDENTITY_NAME=cert-manager +az identity create --name "${IDENTITY_NAME}" +``` + +Grant it permission to modify the DNS zone records: + +```bash +export IDENTITY_CLIENT_ID=$(az identity show --name "${IDENTITY_NAME}" --query 'clientId' -o tsv) +az role assignment create \ + --role "DNS Zone Contributor" \ + --assignee IDENTITY_CLIENT_ID \ + --scope $(az network dns zone show --name $DOMAIN_NAME -o tsv --query id) +``` + +> 📖 Read [What are managed identities for Azure resources?](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) +> for an overview of managed identities and their uses. +> +> 📖 Read [Azure built-in roles](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) to learn about the "DNS Zone Contributor" role. +> +> 📖 Read more about [the `az identity` command](https://learn.microsoft.com/en-us/cli/azure/identity). + +### Add a Federated Identity + +Now associate a federated identity with the managed identity that you created earlier. +cert-manager will authenticate to Azure using a short lived Kubernetes ServiceAccount token, +and it will be able to impersonate the managed identity that you created in the previous step. + +```bash +export SERVICE_ACCOUNT_NAME=cert-manager # ℹ️ This is the default Kubernetes ServiceAccount used by the cert-manager controller. +export SERVICE_ACCOUNT_NAMESPACE=cert-manager # ℹ️ This is the default namespace for cert-manager. +export SERVICE_ACCOUNT_ISSUER=$(az aks show --resource-group $AZURE_DEFAULTS_GROUP --name $CLUSTER --query "oidcIssuerProfile.issuerUrl" -o tsv) +az identity federated-credential create \ + --name "cert-manager" \ + --identity-name "${IDENTITY_NAME}" \ + --issuer "${SERVICE_ACCOUNT_ISSUER}" \ + --subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}" +``` + +- `--subject`: is the distinguishing name of the Kubernetes ServiceAccount. +- `--issuer`: is a URL from which the Azure will download the JWT signing certificate and other metadata + +> 📖 Read about [Workload identity federation](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation) in the Microsoft identity platform documentation. +> +> 📖 Read more about [the `az identity federated-credential` command](https://learn.microsoft.com/en-us/cli/azure/identity/federated-credential). + +### Configure a ClusterIssuer + +For example: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: $EMAIL_ADDRESS + privateKeySecretRef: + name: letsencrypt-staging + solvers: + - dns01: + azureDNS: + hostedZoneName: $AZURE_ZONE_NAME + resourceGroupName: $AZURE_RESOURCE_GROUP + subscriptionID: $AZURE_SUBSCRIPTION_ID + environment: AzurePublicCloud + managedIdentity: + clientID: $IDENTITY_CLIENT_ID +``` + +The following variables need to be filled in. + +```bash +# An email address to which Let's Encrypt will send renewal reminders. +export EMAIL_ADDRESS= +# The Azure DNS zone in which the DNS-01 records will be created and deleted. +export AZURE_ZONE_NAME= +# The Azure resource group containing the DNS zone. +export AZURE_RESOURCE_GROUP= +# The Azure billing account name and ID for the DNS zone. +export AZURE_SUBSCRIPTION= +export AZURE_SUBSCRIPTION_ID=$(az account show --name $AZURE_SUBSCRIPTION --query 'id' -o tsv) +``` + +#### ⚠️ Using 'Ambient Credentials' with ClusterIssuer and Issuer resources + +This authentication method is an example of what cert-manager calls 'ambient credentials'. +Ambient credentials are enabled by default for ClusterIssuer resources, but disabled by default for Issuer resources. +This is to prevent unprivileged users, who have permission to create Issuer resources, from issuing certificates using credentials that cert-manager incidentally has access to. +ClusterIssuer resources are cluster scoped (not namespaced) and only platform administrators should be granted permission to create them. + +If you are using this authentication mechanism and ambient credentials are not enabled, you will see this error: + +```bash +error instantiating azuredns challenge solver: ClientID is not set but neither --cluster-issuer-ambient-credentials nor --issuer-ambient-credentials are set. +``` + +> ⚠️ It is possible (but not recommended) to enable this authentication mechanism for `Issuer` resources, by setting the `--issuer-ambient-credentials` flag on the cert-manager controller to true. + +## Managed Identity Using AAD Pod Identities + +> ⚠️ The [open source Azure AD pod-managed identity (preview) in Azure Kubernetes Service has been deprecated as of 10/24/2022](https://github.com/Azure/aad-pod-identity#-announcement). +> Use Workload Identity instead. + +[AAD Pod Identities](https://azure.github.io/aad-pod-identity) allows assigning a [Managed Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) to a pod. This removes the need for adding explicit credentials into the cluster to create the required DNS records. + +> Note: When using Pod identity, even though assigning multiple identities to a single pod is allowed, currently cert-manager does not support this as it is not able to identify which identity to use. + +Firstly an identity should be created that has access to contribute to the DNS Zone. + +- Example creation using `azure-cli` and `jq`: + +```bash +# Choose a unique Identity name and existing resource group to create identity in. +IDENTITY=$(az identity create --name $IDENTITY_NAME --resource-group $IDENTITY_GROUP --output json) + +# Gets principalId to use for role assignment +PRINCIPAL_ID=$(echo $IDENTITY | jq -r '.principalId') + +# Used for identity binding +CLIENT_ID=$(echo $IDENTITY | jq -r '.clientId') +RESOURCE_ID=$(echo $IDENTITY | jq -r '.id') + +# Get existing DNS Zone Id +ZONE_ID=$(az network dns zone show --name $ZONE_NAME --resource-group $ZONE_GROUP --query "id" -o tsv) + +# Create role assignment +az role assignment create --role "DNS Zone Contributor" --assignee $PRINCIPAL_ID --scope $ZONE_ID +``` + +- Example creation using Terraform + +```terraform +variable resource_group_name {} +variable location {} +variable dns_zone_id {} + +# Creates Identity +resource "azurerm_user_assigned_identity" "dns_identity" { + name = "cert-manager-dns01" + resource_group_name = var.resource_group_name + location = var.location +} + +# Creates Role Assignment +resource "azurerm_role_assignment" "dns_contributor" { + scope = var.dns_zone_id + role_definition_name = "DNS Zone Contributor" + principal_id = azurerm_user_assigned_identity.dns_identity.principal_id +} + +# Client Id Used for identity binding +output "identity_client_id" { + value = azurerm_user_assigned_identity.dns_identity.client_id +} + +# Resource Id Used for identity binding +output "identity_resource_id" { + value = azurerm_user_assigned_identity.dns_identity.id +} +``` + +Next we need to ensure we have installed [AAD Pod Identity](https://azure.github.io/aad-pod-identity) using their walk-through. This will install the CRDs and deployment required to assign the identity. + +Now we can create the identity resource and binding using the below manifest as an example: + +```yaml +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentity +metadata: + annotations: + # recommended to use namespaced identites https://azure.github.io/aad-pod-identity/docs/configure/match_pods_in_namespace/ + aadpodidentity.k8s.io/Behavior: namespaced + name: certman-identity + namespace: cert-manager # change to your preferred namespace +spec: + type: 0 # MSI + resourceID: # Resource Id From Previous step + clientID: # Client Id from previous step +--- +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentityBinding +metadata: + name: certman-id-binding + namespace: cert-manager # change to your preferred namespace +spec: + azureIdentity: certman-identity + selector: certman-label # This is the label that needs to be set on cert-manager pods +``` + +Next we need to ensure the cert-manager pod has a relevant label to use the pod identity binding. This can be done by editing the deployment and adding the below into the `.spec.template.metadata.labels` field + +```yaml +spec: + template: + metadata: + labels: + aadpodidbinding: certman-label # must match selector in AzureIdentityBinding +``` + +Or by using the helm values `podLabels` + +```yaml +podLabels: + aadpodidbinding: certman-label +``` + +Lastly when we create the certificate issuer we only need to specify the `hostedZoneName`, `resourceGroupName` and `subscriptionID` fields for the DNS zone. Example below: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + azureDNS: + subscriptionID: AZURE_SUBSCRIPTION_ID + resourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUP + hostedZoneName: AZURE_DNS_ZONE + # Azure Cloud Environment, default to AzurePublicCloud + environment: AzurePublicCloud +``` + +This authentication mechanism is what cert-manager considers 'ambient credentials'. Use of ambient credentials is disabled by default for cert-manager `Issuer`s. This to ensure unprivileged users who have permission to create issuers cannot issue certificates using any credentials cert-manager incidentally has access to. To enable this authentication mechanism for `Issuer`s, you will need to set `--issuer-ambient-credentials` flag on cert-manager controller to true. (There is a corresponding `--cluster-issuer-ambient-credentials` flag which is set to `true` by default). + +If you are using this authentication mechanism and ambient credentials are not enabled, you will see this error: +```bash +error instantiating azuredns challenge solver: ClientID is not set but neither --cluster-issuer-ambient-credentials nor --issuer-ambient-credentials are set. +``` + +These are necessary to enable Azure Managed Identities. + +## Managed Identity Using AKS Kubelet Identity + +When creating an AKS cluster in Azure there is the option to use a managed identity that is assigned to the kubelet. This identity is assigned to the underlying node pool in the AKS cluster and can then be used by the cert-manager pods to authenticate to Azure Active Directory. + +There are some caveats with this approach, these mainly being: + +- Any permissions granted to this identity will also be accessible to all containers running inside the Kubernetes cluster. +- Using AKS extensions like `Kube Dashboard`, `Virtual Node`, or `HTTP Application Routing` (see full list [here](https://docs.microsoft.com/en-us/azure/aks/use-managed-identity#summary-of-managed-identities)) will create additional identities that are assigned to your node pools. If your node pools have more than one identity assigned, you will need to specify either `clientID` or `resourceID` to select the correct one. + +To set this up, firstly you will need to retrieve the identity that the kubelet is using by querying the AKS cluster. This can then be used to create the appropriate permissions in the DNS zone. + +- Example commands using `azure-cli`: + +```bash +# Get AKS Kubelet Identity +PRINCIPAL_ID=$(az aks show -n $CLUSTERNAME -g $CLUSTER_GROUP --query "identityProfile.kubeletidentity.objectId" -o tsv) + +# Get existing DNS Zone Id +ZONE_ID=$(az network dns zone show --name $ZONE_NAME --resource-group $ZONE_GROUP --query "id" -o tsv) + +# Create role assignment +az role assignment create --role "DNS Zone Contributor" --assignee $PRINCIPAL_ID --scope $ZONE_ID +``` + +- Example terraform: + +```terraform +variable dns_zone_id {} + +# Creating the AKS cluster, abbreviated. +resource "azurerm_kubernetes_cluster" "cluster" { + ... + # Creates Identity associated to kubelet + identity { + type = "SystemAssigned" + } + ... +} + +resource "azurerm_role_assignment" "dns_contributor" { + scope = var.dns_zone_id + role_definition_name = "DNS Zone Contributor" + principal_id = azurerm_kubernetes_cluster.cluster.kubelet_identity[0].object_id + skip_service_principal_aad_check = true # Allows skipping propagation of identity to ensure assignment succeeds. +} +``` + +Then when creating the cert-manager issuer we need to specify the `hostedZoneName`, `resourceGroupName` and `subscriptionID` fields for the DNS Zone. + +We also need to specify `managedIdentity.clientID` or `managedIdentity.resourceID` if multiple managed identities are assigned to the node pools. + +The value for `managedIdentity.clientID` can be fetched by running this command: + +```bash +az aks show -n $CLUSTERNAME -g $CLUSTER_GROUP --query "identityProfile.kubeletidentity.clientId" -o tsv +``` + +Example below: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + azureDNS: + subscriptionID: AZURE_SUBSCRIPTION_ID + resourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUP + hostedZoneName: AZURE_DNS_ZONE + # Azure Cloud Environment, default to AzurePublicCloud + environment: AzurePublicCloud + # optional, only required if node pools have more than 1 managed identity assigned + managedIdentity: + # client id of the node pool managed identity (can not be set at the same time as resourceID) + clientID: YOUR_MANAGED_IDENTITY_CLIENT_ID + # resource id of the managed identity (can not be set at the same time as clientID) + # resourceID: YOUR_MANAGED_IDENTITY_RESOURCE_ID +``` + +## Service Principal + +Configuring the AzureDNS DNS01 Challenge for a Kubernetes cluster requires +creating a service principal in Azure. + +To create the service principal you can use the following script (requires +`azure-cli` and `jq`): + +```bash +# Choose a name for the service principal that contacts azure DNS to present +# the challenge. +$ AZURE_CERT_MANAGER_NEW_SP_NAME=NEW_SERVICE_PRINCIPAL_NAME +# This is the name of the resource group that you have your dns zone in. +$ AZURE_DNS_ZONE_RESOURCE_GROUP=AZURE_DNS_ZONE_RESOURCE_GROUP +# The DNS zone name. It should be something like domain.com or sub.domain.com. +$ AZURE_DNS_ZONE=AZURE_DNS_ZONE + +$ DNS_SP=$(az ad sp create-for-rbac --name $AZURE_CERT_MANAGER_NEW_SP_NAME --output json) +$ AZURE_CERT_MANAGER_SP_APP_ID=$(echo $DNS_SP | jq -r '.appId') +$ AZURE_CERT_MANAGER_SP_PASSWORD=$(echo $DNS_SP | jq -r '.password') +$ AZURE_TENANT_ID=$(echo $DNS_SP | jq -r '.tenant') +$ AZURE_SUBSCRIPTION_ID=$(az account show --output json | jq -r '.id') +``` + +For security purposes, it is appropriate to utilize RBAC to ensure that you +properly maintain access control to your resources in Azure. The service +principal that is generated by this tutorial has fine-grained access to ONLY the +DNS Zone in the specific resource group specified. It requires this permission +so that it can read/write the \_acme\_challenge TXT records to the zone. + +Lower the Permissions of the service principal. + +```bash +$ az role assignment delete --assignee $AZURE_CERT_MANAGER_SP_APP_ID --role Contributor +``` + +Give Access to DNS Zone. + +```bash +$ DNS_ID=$(az network dns zone show --name $AZURE_DNS_ZONE --resource-group $AZURE_DNS_ZONE_RESOURCE_GROUP --query "id" --output tsv) +$ az role assignment create --assignee $AZURE_CERT_MANAGER_SP_APP_ID --role "DNS Zone Contributor" --scope $DNS_ID +``` + +Check Permissions. As the result of the following command, we would like to see just one object in the permissions array with "DNS Zone Contributor" role. + +```bash +$ az role assignment list --all --assignee $AZURE_CERT_MANAGER_SP_APP_ID +``` + +A secret containing service principal password should be created on Kubernetes to facilitate presenting the challenge to Azure DNS. You can create the secret with the following command: + +```bash +$ kubectl create secret generic azuredns-config --from-literal=client-secret=$AZURE_CERT_MANAGER_SP_PASSWORD +``` + +Get the variables for configuring the issuer. + +```bash +$ echo "AZURE_CERT_MANAGER_SP_APP_ID: $AZURE_CERT_MANAGER_SP_APP_ID" +$ echo "AZURE_CERT_MANAGER_SP_PASSWORD: $AZURE_CERT_MANAGER_SP_PASSWORD" +$ echo "AZURE_SUBSCRIPTION_ID: $AZURE_SUBSCRIPTION_ID" +$ echo "AZURE_TENANT_ID: $AZURE_TENANT_ID" +$ echo "AZURE_DNS_ZONE: $AZURE_DNS_ZONE" +$ echo "AZURE_DNS_ZONE_RESOURCE_GROUP: $AZURE_DNS_ZONE_RESOURCE_GROUP" +``` + +To configure the issuer, substitute the capital cased variables with the values +from the previous script. You can get the subscription id from the Azure portal. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + azureDNS: + clientID: AZURE_CERT_MANAGER_SP_APP_ID + clientSecretSecretRef: + # The following is the secret we created in Kubernetes. Issuer will use this to present challenge to Azure DNS. + name: azuredns-config + key: client-secret + subscriptionID: AZURE_SUBSCRIPTION_ID + tenantID: AZURE_TENANT_ID + resourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUP + hostedZoneName: AZURE_DNS_ZONE + # Azure Cloud Environment, default to AzurePublicCloud + environment: AzurePublicCloud +``` diff --git a/content/v1.12-docs/configuration/acme/dns01/cloudflare.md b/content/v1.12-docs/configuration/acme/dns01/cloudflare.md new file mode 100644 index 0000000000..b3f4ded449 --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/cloudflare.md @@ -0,0 +1,108 @@ +--- +title: Cloudflare +description: 'cert-manager configuration: ACME DNS-01 challenges using Cloudflare DNS' +--- + +To use Cloudflare, you may use one of two types of tokens. **API Tokens** allow application-scoped keys bound to specific zones and permissions, while **API Keys** are globally-scoped keys that carry the same permissions as your account. + +**API Tokens** are recommended for higher security, since they have more restrictive permissions and are more easily revocable. + +## API Tokens + +Tokens can be created at **User Profile > API Tokens > API Tokens**. The following settings are recommended: + +- Permissions: + - `Zone - DNS - Edit` + - `Zone - Zone - Read` +- Zone Resources: + - `Include - All Zones` + +To create a new `Issuer`, first make a Kubernetes secret containing your new API token: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: cloudflare-api-token-secret +type: Opaque +stringData: + api-token: +``` + +Then in your `Issuer` manifest: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + apiTokenSecretRef: + name: cloudflare-api-token-secret + key: api-token +``` + +## API Keys + +API keys can be retrieved at **User Profile > API Tokens > API Keys > Global API Key > View**. + +To create a new `Issuer`, first make a Kubernetes secret containing your API key: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: cloudflare-api-key-secret +type: Opaque +stringData: + api-key: +``` + +Then in your `Issuer` manifest: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + email: my-cloudflare-acc@example.com + apiKeySecretRef: + name: cloudflare-api-key-secret + key: api-key +``` + +## Troubleshooting + +### Actor `com.cloudflare.api.token.xxxx` requires permission `com.cloudflare.api.account.zone.list` to list zones +If you get the error that your token does not have the correct permission to list zones there can be 2 causes. +1. The token lacks the `Zone - Zone - Read` permission +2. cert-manager identified the wrong zone name for the domain due to DNS issues. + +In the case of the 2nd issue you will see an error like below: +``` +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Started 6s cert-manager Challenge scheduled for processing + Warning PresentError 3s (x2 over 3s) cert-manager Error presenting challenge: Cloudflare API Error for GET "/zones?name=" + Error: 0: Actor 'com.cloudflare.api.token.xxxx' requires permission 'com.cloudflare.api.account.zone.list' to list zones +``` + +In this case we recommend [changing your DNS01 self-check nameservers](./README.md#setting-nameservers-for-dns01-self-check). + +## `Cloudflare API error for POST "/zones//dns_records` generic error + +You might be hitting this as Cloudflare blocks the use of the API to update DNS records for the following TLDs: `.cf`, `.ga`, `.gq`, `.ml` and `.tk`. +This is discussed in the [Cloudflare Community](https://community.cloudflare.com/t/unable-to-update-ddns-using-api-for-some-tlds/167228). +We recommend using an alternative DNS provider when using these TLDs. \ No newline at end of file diff --git a/content/v1.12-docs/configuration/acme/dns01/digitalocean.md b/content/v1.12-docs/configuration/acme/dns01/digitalocean.md new file mode 100644 index 0000000000..4d6c155863 --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/digitalocean.md @@ -0,0 +1,46 @@ +--- +title: DigitalOcean +description: 'cert-manager configuration: ACME DNS-01 challenges using DigitalOcean DNS' +--- + +This provider uses a Kubernetes `Secret` resource to work. In the following +example, the `Secret` will have to be named `digitalocean-dns` and have a +sub-key `access-token` with the token in it. For example: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: digitalocean-dns +data: + # insert your DO access token here + access-token: "base64 encoded access-token here" + ``` + +The access token must have write access. + +To create a Personal Access Token, see [DigitalOcean documentation](https://docs.digitalocean.com/reference/api/create-personal-access-token/). + +Handy direct link: https://cloud.digitalocean.com/account/api/tokens/new + +To encode your access token into base64, you can use the following + +```bash +echo -n 'your-access-token' | base64 +``` + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + digitalocean: + tokenSecretRef: + name: digitalocean-dns + key: access-token +``` \ No newline at end of file diff --git a/content/v1.12-docs/configuration/acme/dns01/google.md b/content/v1.12-docs/configuration/acme/dns01/google.md new file mode 100644 index 0000000000..460239de80 --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/google.md @@ -0,0 +1,242 @@ +--- +title: Google CloudDNS +description: 'cert-manager configuration: ACME DNS-01 challenges using Google CloudDNS' +--- + +This guide explains how to set up an `Issuer`, or `ClusterIssuer`, to use Google +CloudDNS to solve DNS01 ACME challenges. It's advised you read the [DNS01 +Challenge Provider](./README.md) page first for a more general understanding of +how cert-manager handles DNS01 challenges. + +This guide assumes that your cluster is hosted on Google Cloud Platform (GCP) +and that you already have a domain set up with CloudDNS. + +You'll need to be using a **Public DNS Zone**, so that the ACME challenge checker +is able to access the DNS records that cert-manager will create. + +## Set up a Service Account + +cert-manager needs to be able to add records to CloudDNS in order to solve the +DNS01 challenge. To enable this, a GCP service account must be created with the +`dns.admin` role. + +> Note: For this guide the `gcloud` command will be used to set up the service +> account. Ensure that `gcloud` is using the correct project and zone before +> entering the commands. These steps could also be completed using the Cloud +> Console. + +```bash +PROJECT_ID=myproject-id +gcloud iam service-accounts create dns01-solver --display-name "dns01-solver" +``` + +In the command above, replace `myproject-id` with the ID of your project. + +```bash +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member serviceAccount:dns01-solver@$PROJECT_ID.iam.gserviceaccount.com \ + --role roles/dns.admin +``` + +> **Note**: The use of the `dns.admin` role in this example role is for convenience. +> If you want to ensure cert-manager runs under a least privilege service account, +> you will need to create a custom role with the following permissions: +> +> * `dns.resourceRecordSets.*` +> * `dns.changes.*` +> * `dns.managedZones.list` + +## Use Static Credentials + +Follow the instructions in the following sections to deploy cert-manager using +static credentials for the service account you created. You should rotate these +credentials periodically. + +### Create a Service Account Secret + +To access this service account, cert-manager uses a key stored in a Kubernetes +`Secret`. First, create a key for the service account and download it as a JSON +file, then create a `Secret` from this file. + +Keep the key file safe and do not share it, as it could be used to gain access +access to your cloud resources. The key file can be deleted once it has been +used to generate the `Secret`. + +If you did not create the service account `dns01-solver` before, you need to +create it first. + +```bash +gcloud iam service-accounts create dns01-solver +``` + +Replace instances of `$PROJECT_ID` with the ID of your project. +```bash +gcloud iam service-accounts keys create key.json \ + --iam-account dns01-solver@$PROJECT_ID.iam.gserviceaccount.com +kubectl create secret generic clouddns-dns01-solver-svc-acct \ + --from-file=key.json +``` + +> Note: If you have already added the `Secret` but get an error: `...due to +> error processing: error getting clouddns service account: secret "XXX" not +> found`, the `Secret` may be in the wrong namespace. If you're configuring a +> `ClusterIssuer`, move the `Secret` to the `Cluster Resource Namespace` which +> is `cert-manager` by default. If you're configuring an `Issuer`, the `Secret` +> should be stored in the same namespace as the `Issuer` resource. + +### Create an Issuer That Uses CloudDNS + +Next, create an `Issuer` (or `ClusterIssuer`) with a `cloudDNS` provider. An +example `Issuer` manifest can be seen below with annotations. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + cloudDNS: + # The ID of the GCP project + project: $PROJECT_ID + # This is the secret used to access the service account + serviceAccountSecretRef: + name: clouddns-dns01-solver-svc-acct + key: key.json +``` + +For more information about `Issuers` and `ClusterIssuers`, see +[Configuration](../../README.md). + +Once an `Issuer` (or `ClusterIssuer`) has been created successfully, a +`Certificate` can then be added to verify that everything works. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: default +spec: + secretName: example-com-tls + issuerRef: + # The issuer created previously + name: example-issuer + dnsNames: + - example.com + - www.example.com +``` + +For more details about `Certificates`, see [Usage](../../../usage/README.md). + +## GKE Workload Identity + +If you are deploying cert-manager into a [Google Container Engine (GKE) +cluster](https://cloud.google.com/kubernetes-engine/) with workload identity +enabled, you can leverage workload identity to avoid creating and managing +static service account credentials. The [workload identity +how-to](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) +provides more detail on how workload identity functions, but briefly workload +identity allows you to link a Google service accounts (GSA) to Kubernetes +service accounts (KSA). This GSA/KSA linking is two-way, i.e., you must +establish the link in GCP _and_ Kubernetes. Once configured, workload identity +allows Kubernetes pods running under a KSA to access the GCP APIs with the +permissions of the linked GSA. The workload identity how-to also provides +detailed instructions on how to enable workload identity in your GKE cluster. +The instructions in the following sections assume you are deploying cert-manager +to a GKE cluster with workload identity already enabled. + +### Enable Ambient Credential Usage + +'Ambient Credentials' are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the ClusterIssuer API object. When this flag is enabled Cert-Manager will access the GKE Metadata server for credentials. By default this is enabled for ClusterIssuer resources but is disabled for Issuer resources. To enable it for Issuer resources set the `--issuer-ambient-credentials` flag. + +### Link KSA to GSA in GCP + +The cert-manager component that needs to modify DNS records is the pod created +as part of the cert-manager deployment. The [standard methods for deploying +cert-manager to Kubernetes](../../../installation/README.md) create the +cert-manager deployment in the cert-manager namespace and its pod spec specifies +it runs under the cert-manager service account. To link the GSA you created +above to the cert-manager KSA in the cert-manager namespace in your GKE cluster, +run the following command. + +```bash +gcloud iam service-accounts add-iam-policy-binding \ + --role roles/iam.workloadIdentityUser \ + --member "serviceAccount:$PROJECT_ID.svc.id.goog[cert-manager/cert-manager]" \ + dns01-solver@$PROJECT_ID.iam.gserviceaccount.com +``` + +If your cert-manager pods are running under a different service account, replace +`goog[cert-manager/cert-manager]` with `goog[NAMESPACE/SERVICE_ACCOUNT]`, where +`NAMESPACE` is the namespace of the service account and `SERVICE_ACCOUNT` is the +name of the service account. + +### Link KSA to GSA in Kubernetes + +After deploying cert-manager, add the proper workload identity annotation to the +cert-manager service account. + +```bash +kubectl annotate serviceaccount --namespace=cert-manager cert-manager \ + "iam.gke.io/gcp-service-account=dns01-solver@$PROJECT_ID.iam.gserviceaccount.com" +``` + +Again, if your cert-manager pods are running under a different service account, +replace `--namespace=cert-manager cert-manager` with `--namespace=NAMESPACE +SERVICE_ACCOUNT`, where `NAMESPACE` is the namespace of the service account and +`SERVICE_ACCOUNT` is the name of the service account. + +If you are deploying cert-manager using its helm chart, you can use the +`serviceAccount.annotations` configuration parameter to add the above workload +identity annotation to the cert-manager KSA. + +### Create an Issuer That Uses CloudDNS + +Next, create an `Issuer` (or `ClusterIssuer`) with a `clouddns` provider. An +example `Issuer` manifest can be seen below with annotations. Note that the +issuer does not include a `serviceAccountSecretRef` property. Excluding this +instructs cert-manager to use the default credentials provided by GKE workload +identity. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + cloudDNS: + # The ID of the GCP project + project: $PROJECT_ID +``` + +For more information about `Issuers` and `ClusterIssuers`, see +[Configuration](../../README.md). + +Once an `Issuer` (or `ClusterIssuer`) has been created successfully, a +`Certificate` can then be added to verify that everything works. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: default +spec: + secretName: example-com-tls + issuerRef: + # The issuer created previously + name: example-issuer + dnsNames: + - example.com + - www.example.com +``` + +For more details about `Certificates`, see [Usage](../../../usage/README.md). diff --git a/content/v1.12-docs/configuration/acme/dns01/rfc2136.md b/content/v1.12-docs/configuration/acme/dns01/rfc2136.md new file mode 100644 index 0000000000..86e9dd32df --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/rfc2136.md @@ -0,0 +1,207 @@ +--- +title: RFC-2136 +description: 'cert-manager configuration: ACME DNS-01 challenges using RFC2136-compliant DNS providers' +--- + +The goal of this document is to provide a configuration overview of the various +facilities required to deploy cert-manager against a RFC2136 compliant DNS +server such as BIND `named`. This capability is also commonly known as “dynamic +DNS”. + +Unlike the peer of other cert-manager DNS integrations, `named` is a bit of a +“Swiss Army Knife” of domain name servers. Over the years, it has been highly +optimized to provide maximal vertical scalability for a single node, as well as +horizontal scalability with service provider interfaces. This flexibility makes +it impossible to go into every possible `named` deployment that a user may run +in to though. Instead, this document will try to make sure your server is ready +to accept requests from cert-manager using command line tools, then get on to +the making the two work together. + +## Transaction Signatures ⇒ TSIG + +Dynamic DNS updates are essentially server queries which otherwise might return +resource records (RRs). Since DNS servers are commonly exposed to the public +internet, being able to push an unauthenticated update to any server that +responds to queries would be immediately untenable. + +In the eyes of the `named` architects, the generic solution to this problem +space was twofold. The first is to require manual enablement of updates at a +zone level, such as `example.com`. In a naive network, there is no requirement +that zone updates have any security to them, and clients can be configured such +that they can provide updates without any authentication. An example of where +this is useful is for machines booting using DHCP, in this case the machines +know about themselves and the DNS server can be configured to accept updates +when they come from the address being configured. + +This clearly has limitations in situations such as cert-manager and the DNS01 +challenge. In this environment, a TXT RR must be created after coordination with +the ACME server. After negotiating with the ACME server, a the TXT RR that is +published on the domain validates that the domain is legitimately engaged with +the process of creating a certificate for it. In the bigger picture of DNS, this +means that an arbitrary actor (cert-manager, in this case) must be able to add +one of these KV mappings to the domain and delete it after the certificate has +been issued. `cert-manager` does not have a convenient physical characteristic +such as a DHCP allocation to validate it's requests. + +For cases like this, we need to be able to sign a request that is being sent to +the DNS server. We do that through TSIGs, or Transaction SIGnatures. + +### Configuration Step 1 - Set up your DNS server for secure dynamic updates + +There are many excellent tutorials on the net that walk through +preparing a basic `named` server for dynamic updates: + +- https://www.cyberciti.biz/faq/unix-linux-bind-named-configuring-tsig/ +- https://tomthorp.me/blog/using-tsig-enable-secure-zone-transfers-between-bind-9x-servers + +More complex `name` deployments will not use text files, but rather may use LDAP +or SQL for a database for resource records. An additional wrinkle is metadata +configuration, such as for zone metadata like enabling dynamic updates or access +control lists (ACLs) for a zone. There are too many configurations to go into +here, but you should be able to find the documentation to do so. + +Whatever your deployment is, the goal at this stage has nothing to do with +cert-manager and everything to do with a tool called `nsupdate` generating +updates signed with TSIG. Once this is out of the way, you can attack the +cert-manager configuration with far greater confidence. + +#### Using `nsupdate` + +Most paths to configuring BIND `named` will go through using `dnssec-keygen`. +This command-line tool generates a named private key that is used for signing +TSIG requests. When a request is signed, both the signature and the name of the +private key are attached to the request in an unencrypted form. In this manner, +when the request is received, the name of the private key can be used to by the +recipient to find the private key itself, build a new signature with it, and +compare the two for acceptance. + +Since there are dozens of ways to have your `named` server misconfigured, we’ll +use `nsupdate` to test that the server behaves as expected before we get there. +`https://debian-administration.org/article/591/Using_the_dynamic_DNS_editor_nsupdate` +is a solid breakdown of how to use the tool. + +To get started, we’ll simply run `nsupdate -k ` where `keyID` is the value +returned from `dnssec-keygen`. This will read the key from disk and provide a +command prompt to issue commands. In general, we want to write a simple TXT RR +and make sure we can delete it. + +```bash +$ nsupdate -k +update add www1.example.com 60 txt testing +send +… test here with `nslookup` +update delete www1.example.com txt +send +… test here with `nslookup` +``` + +Any failures to write, read or delete the record will mean that cert-manager +will not be able to do so either, no matter how well it is configured. + +### Configuration Step 2 - Set up cert-manager + +Now we get to the fun stuff, seeing everything work. Remember that we need to +set up the ACME DNS01 issuer and challenge mechanism as well as the `rfc2136` +provider. Since the documentation covers the other parts sufficiently, let’s +focus on the provider here. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + rfc2136: + nameserver:
+ tsigKeyName: + tsigAlgorithm: HMACSHA512 // should be matched to the algo you chose in `dnssec-keygen` + tsigSecretSecretRef: + name: + key: +``` + +For example: + +```yaml + rfc2136: + nameserver: 1.2.3.4:53 + tsigKeyName: example-com-secret + tsigAlgorithm: HMACSHA512 + tsigSecretSecretRef: + name: tsig-secret + key: tsig-secret-key +``` + +For this example configuration, we’ll need the following two commands. The +first, on your `named` server generates the key. Note how `example-com-secret` +is both in the `tsigKeyName` above and the `dnssec-keygen` command that follows. + +```bash +$ dnssec-keygen -r /dev/urandom -a HMAC-SHA512 -b 512 -n HOST example-com-secret +``` + +Also note how the `tsigAlgorithm` is provided in both the configuration and the +`keygen` command. They are listed at +`https://github.com/miekg/dns/blob/v1.0.12/tsig.go#L18-L23`. + +The second bit of configuration you need on the Kubernetes side is to create a +secret. Pulling the secret key string from the `.private` file generated +above, use the secret in the placeholder below: + +```bash +$ kubectl -n cert-manager create secret generic tsig-secret --from-literal=tsig-secret-key= +``` + +Note how the `tsig-secret` and `tsig-secret-key` match the configuration in the +`tsigSecretSecretRef` above. + +## Rate Limits + +The `rfc2136` provider waits until *all* nameservers to in your domain's SOA RR +respond with the same result before it contacts Let's Encrypt to complete the +challenge process. This is because the challenge server contacts a +non-authoritative DNS server that does a recursive query (a query for records it +does not maintain locally). If the servers in the SOA do not contain the correct +values, it's likely that the non-authoritative server will have bad information +as well, causing the request to go against rate limits and eventually locking +the process out. + +This process is in place to protect users from server misconfiguration creating +a more subtle lockout that persists after the server configuration has been +repaired. + +As documented elsewhere, it is prudent to fully debug configurations using the +ACME staging servers before using the production servers. The staging servers +have less aggressive rate limits, but the certificates they issue are not signed +with a root certificate trusted by browsers. + +## What’s next? + +This configuration so far will actually do nothing. You still have to request a +certificate as described [here](../../../usage/README.md). Once a certificate is +requested, the provider will begin processing the request. + +## Troubleshooting + +- Be sure that you have fully tested the DNS server updates using `nsupdate` + first. Ideally, this is done from a pod in the same namespace as the `rfc2136` + provider to ensure there are no firewall issues. +- The logs for the `cert-manager` pod are your friend. Additional logs can be + generated by adding the `--v=5` argument to the container launch. +- The TSIG key is encoded with `base64`, but the Kubernetes API server also + expects that key literals will be decoded before they are stored. In some + cases, a key must be double-encoded. (If you've tested using `nsupdate`, it's + pretty easy to spot when you are running into this.) +- Pay attention to the refresh time of the zone you are working with. For zones + with low traffic, it will not make a significant difference to reduce the + refresh time down to about five minutes while getting initial certificates. + Once the process is working, the beauty of `cert-manager` is it doesn't matter + if a renewal takes hours due to refresh times, it's all automated! +- Compared to the other providers that often use REST APIs to modify DNS RRs, + this provider can take a little longer. You can `watch kubectl certificate + yourcert` to get a display of what's going on. It's not uncommon for the process + to take five minutes in total. \ No newline at end of file diff --git a/content/v1.12-docs/configuration/acme/dns01/route53.md b/content/v1.12-docs/configuration/acme/dns01/route53.md new file mode 100644 index 0000000000..f18bbf128c --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/route53.md @@ -0,0 +1,253 @@ +--- +title: Route53 +description: 'cert-manager configuration: ACME DNS-01 challenges using Amazon AWS Route53 DNS' +--- + +This guide explains how to set up an `Issuer`, or `ClusterIssuer`, to use Amazon +Route53 to solve DNS01 ACME challenges. It's advised you read the [DNS01 +Challenge Provider](./README.md) page first for a more general understanding of +how cert-manager handles DNS01 challenges. + +> Note: This guide assumes that your cluster is hosted on Amazon Web Services +> (AWS) and that you already have a hosted zone in Route53. + +## Set up an IAM Role + +cert-manager needs to be able to add records to Route53 in order to solve the +DNS01 challenge. To enable this, create a IAM policy with the following +permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "route53:GetChange", + "Resource": "arn:aws:route53:::change/*" + }, + { + "Effect": "Allow", + "Action": [ + "route53:ChangeResourceRecordSets", + "route53:ListResourceRecordSets" + ], + "Resource": "arn:aws:route53:::hostedzone/*" + }, + { + "Effect": "Allow", + "Action": "route53:ListHostedZonesByName", + "Resource": "*" + } + ] +} +``` + +> Note: The `route53:ListHostedZonesByName` statement can be removed if you +> specify the (optional) `hostedZoneID`. You can further tighten the policy by +> limiting the hosted zone that cert-manager has access to (e.g. +> `arn:aws:route53:::hostedzone/DIKER8JEXAMPLE`). + +## Credentials + +You have two options for the set up - either create a user or a role and attach +that policy from above. Using a role is considered best practice because you do +not have to store permanent credentials in a secret. + +cert-manager supports two ways of specifying credentials: + +- explicit by providing a `accessKeyID` and `secretAccessKey` +- or implicit (using [metadata + service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) + or [environment variables or credentials + file](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials). + +cert-manager also supports specifying a `role` to enable cross-account access +and/or limit the access of cert-manager. Integration with +[`kiam`](https://github.com/uswitch/kiam) and +[`kube2iam`](https://github.com/jtblin/kube2iam) should work out of the box. + + +## Cross Account Access + +Example: Account Y manages Route53 DNS Zones. Now you want cert-manager running in Account X (or many other accounts) to be able to manage records in Route53 zones hosted in Account Y. + +First, create a role with the permissions policy above (let's call the role `dns-manager`) +in Account Y, and attach a trust relationship like the one below. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::XXXXXXXXXXX:role/cert-manager" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` + +Bear in mind, that you won't be able to define this policy until `cert-manager` role on account Y is created. If you are setting this up using a configuration language, you may want to define principal as: + +```json +"Principal": { + "AWS": "XXXXXXXXXXX" + } +``` +And restrict it, in a future step, after all the roles are created. + +This allows the role `cert-manager` in Account X to assume the `dns-manager` role in Account Y to manage the Route53 DNS zones in Account Y. For more information visit the [official +documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html). + +Second, create the cert-manager role in Account X; this will be used as a credentials source for the cert-manager pods running in Account X. Attach to the role the following **permissions** policy: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": "arn:aws:iam::YYYYYYYYYYYY:role/dns-manager", + "Action": "sts:AssumeRole" + } + ] +} +``` + +And the following trust relationship (Add AWS `Service`s as needed): + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` + +## Creating an Issuer (or `ClusterIssuer`) + +Here is an example configuration for a `ClusterIssuer`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + ... + solvers: + + # example: cross-account zone management for example.com + # this solver uses ambient credentials (i.e. inferred from the environment or EC2 Metadata Service) + # to assume a role in a different account + - selector: + dnsZones: + - "example.com" + dns01: + route53: + region: us-east-1 + hostedZoneID: DIKER8JEXAMPLE # optional, see policy above + role: arn:aws:iam::YYYYYYYYYYYY:role/dns-manager + + # this solver handles example.org challenges + # and uses explicit credentials + - selector: + dnsZones: + - "example.org" + dns01: + route53: + region: eu-central-1 + accessKeyID: AKIAIOSFODNN7EXAMPLE + secretAccessKeySecretRef: + name: prod-route53-credentials-secret + key: secret-access-key + # you can also assume a role with these credentials + role: arn:aws:iam::YYYYYYYYYYYY:role/dns-manager +``` + +Note that, as mentioned above, the pod is using `arn:aws:iam::XXXXXXXXXXX:role/cert-manager` as a credentials source in Account X, but the `ClusterIssuer` ultimately assumes the `arn:aws:iam::YYYYYYYYYYYY:role/dns-manager` role to actually make changes in Route53 zones located in Account Y. + +## EKS IAM Role for Service Accounts (IRSA) + +While [`kiam`](https://github.com/uswitch/kiam) / [`kube2iam`](https://github.com/jtblin/kube2iam) work directly with cert-manager, some special attention is needed for using the [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) feature available on EKS. + +### OIDC provider + +First follow the AWS documentation [Enabling IAM roles for service accounts on your cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) to ensure that the OIDC provider for the EKS cluster is enabled. The OIDC information is needed to create the trust relationship for the cert-manager role below. + +### IAM role trust policy + +The cert-manager role needs the following trust relationship attached to the role in order to use the IRSA method. Replace the following: + +- `` with the AWS account ID of the EKS cluster. +- `` with the region where the EKS cluster is located. +- `` with the hash in the EKS API URL; this will be a random 32 character hex string (example: `45DABD88EEE3A227AF0FA468BE4EF0B5`) +- `` with the namespace where cert-manager is running. +- `` with the name of the `ServiceAccount` object created by cert-manager. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRoleWithWebIdentity", + "Principal": { + "Federated": "arn:aws:iam:::oidc-provider/oidc.eks..amazonaws.com/id/" + }, + "Condition": { + "StringEquals": { + "oidc.eks..amazonaws.com/id/:sub": "system:serviceaccount::" + } + } + } + ] +} +``` + +**Note:** If you're following the Cross Account example above, this trust policy is attached to the cert-manager role in Account X with ARN `arn:aws:iam::XXXXXXXXXXX:role/cert-manager`. The permissions policy is the same as above. + +### Service annotation + +Annotate the `ServiceAccount` created by cert-manager: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXX:role/cert-manager +``` + +You will also need to modify the cert-manager `Deployment` with the correct file system permissions, so the `ServiceAccount` token can be read. + +```yaml +spec: + template: + spec: + securityContext: + fsGroup: 1001 +``` + +The cert-manager Helm chart provides a variable for injecting annotations into cert-manager's `ServiceAccount` and `Deployment` object like so: + +```yaml +serviceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXX:role/cert-manager +securityContext: + fsGroup: 1001 +``` + +**Note:** If you're following the Cross Account example above, modify the `ClusterIssuer` in the same way as above with the role from Account Y. diff --git a/content/v1.12-docs/configuration/acme/dns01/webhook.md b/content/v1.12-docs/configuration/acme/dns01/webhook.md new file mode 100644 index 0000000000..d9a4339e39 --- /dev/null +++ b/content/v1.12-docs/configuration/acme/dns01/webhook.md @@ -0,0 +1,32 @@ +--- +title: Webhook +description: 'cert-manager configuration: ACME DNS-01 challenges using External Webhook Solvers' +--- + +The webhook `Issuer` is a generic ACME solver. The actual work is done by an +external service. Look at the respective documentation of +[`dns-providers`](../../../contributing/dns-providers.md). + +View more webhook solvers at https://github.com/topics/cert-manager-webhook. + +Here is an example of how webhook providers are to be configured. All `DNS01` +providers will contain their own specific configuration however all require a +`groupName` and `solverName` field. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + webhook: + groupName: $WEBHOOK_GROUP_NAME + solverName: $WEBHOOK_SOLVER_NAME + config: + ... + +``` diff --git a/content/v1.12-docs/configuration/acme/http01/README.md b/content/v1.12-docs/configuration/acme/http01/README.md new file mode 100644 index 0000000000..ee46fdbe40 --- /dev/null +++ b/content/v1.12-docs/configuration/acme/http01/README.md @@ -0,0 +1,366 @@ +--- +title: HTTP01 +description: 'cert-manager configuration: ACME HTTP-01 challenges' +--- + +
+ +📌 This page focuses on solving ACME HTTP-01 challenges. If you are looking for +how to automatically create Certificate resources by annotating Ingress or +Gateway resources, see [Securing Ingress Resources](../../../usage/ingress.md) and +[Securing Gateway Resources](../../../usage/gateway.md). + +
+ +cert-manager uses your existing Ingress or Gateway configuration in order to +solve HTTP01 challenges. + + +## Configuring the HTTP01 Ingress solver + +This page contains details on the different options available on the `Issuer` +resource's HTTP01 challenge solver configuration. For more information on +configuring ACME issuers and their API format, read the [ACME Issuers](../README.md) +documentation. + +You can read about how the HTTP01 challenge type works on the [Let's Encrypt +challenge types +page](https://letsencrypt.org/docs/challenge-types/#http-01-challenge). + +Here is an example of a simple `HTTP01` ACME issuer with more options for +configuration below: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + privateKeySecretRef: + name: example-issuer-account-key + solvers: + - http01: + ingress: + class: nginx +``` + +## Options + +The HTTP01 Issuer supports a number of additional options. For full details on +the range of options available, read the [reference +documentation](../../../reference/api-docs.md#acme.cert-manager.io/v1.ACMEChallengeSolverHTTP01). + +### `class` + +If the `class` field is specified, cert-manager will create new `Ingress` +resources in order to route traffic to the `acmesolver` pods, which are +responsible for responding to ACME challenge validation requests. + +If this field is not specified, and `name` is also not specified, +cert-manager will default to create *new* `Ingress` resources but will **not** +set the ingress class on these resources, meaning *all* ingress controllers +installed in your cluster will serve traffic for the challenge solver, +potentially incurring additional cost. + + +### `name` + +If the `name` field is specified, cert-manager will edit the named +ingress resource in order to solve HTTP01 challenges. + +This is useful for compatibility with ingress controllers such as `ingress-gce`, +which utilize a unique IP address for each `Ingress` resource created. + +This mode should be avoided when using ingress controllers that expose a single +IP for all ingress resources, as it can create compatibility problems with +certain ingress-controller specific annotations. + +

`serviceType`

+ +In rare cases it might be not possible/desired to use `NodePort` as type for the +HTTP01 challenge response service, e.g. because of Kubernetes limit +restrictions. To define which Kubernetes service type to use during challenge +response specify the following HTTP01 configuration: + +```yaml + http01: + ingress: + # Valid values are ClusterIP and NodePort + serviceType: ClusterIP +``` + +By default, type `NodePort` will be used when you don't set HTTP01 or when you set +`serviceType` to an empty string. Normally there's no need to change this. + + +### `podTemplate` + +You may wish to change or add to the labels and annotations of solver pods. +These can be configured under the `metadata` field under `podTemplate`. + +Similarly, you can set the `nodeSelector`, tolerations and affinity of solver +pods by configuring under the `spec` field of the `podTemplate`. No other +spec fields can be edited. + +An example of how you could configure the template is as so: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ... +spec: + acme: + server: ... + privateKeySecretRef: + name: ... + solvers: + - http01: + ingress: + podTemplate: + metadata: + labels: + foo: "bar" + env: "prod" + spec: + nodeSelector: + bar: baz +``` + +The added labels and annotations will merge on top of the cert-manager defaults, +overriding entries with the same key. + +No other fields of the `podTemplate` exist. + +### `ingressTemplate` + +It is possible to add labels and annotations to the solver ingress resources. +It can be really useful when you are managing several Ingress Controllers across your cluster and you want to make sure that the right one will pick up and expose the solver (for the upcoming challenge to resolve). +These can be configured under the `metadata` field under `ingressTemplate`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ... +spec: + acme: + server: ... + privateKeySecretRef: + name: ... + solvers: + - http01: + ingress: + ingressTemplate: + metadata: + labels: + foo: "bar" + annotations: + "nginx.ingress.kubernetes.io/whitelist-source-range": "0.0.0.0/0,::/0" + "nginx.org/mergeable-ingress-type": "minion" + "traefik.ingress.kubernetes.io/frontend-entry-points": "http" +``` + +The added labels and annotations will merge on top of the cert-manager defaults, +overriding entries with the same key. + +No other fields of the ingress can be edited. + +## Configuring the HTTP-01 Gateway API solver + +**FEATURE STATE**: cert-manager 1.5 [alpha] + +The Gateway and HTTPRoute resources are part of the [Gateway API][gwapi], a set +of CRDs that you install on your Kubernetes cluster that provide various +improvements over the Ingress API. + +[gwapi]: https://gateway-api.sigs.k8s.io + +
+ +📌 This feature requires the installation of the [Gateway API bundle](https://gateway-api.sigs.k8s.io/guides/#installing-a-gateway-controller) and passing a +feature flag to the cert-manager controller. + +To install v1.5.1 Gateway API bundle (Gateway CRDs and webhook), run the following command: + +```sh +kubectl apply -f "https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.5.1/standard-install.yaml" +``` + +To enable the feature in cert-manager, turn on the `GatewayAPI` feature gate: + +- If you are using Helm: + + ```sh + helm upgrade --install cert-manager jetstack/cert-manager --namespace cert-manager \ + --set "extraArgs={--feature-gates=ExperimentalGatewayAPISupport=true}" + ``` + +- If you are using the raw cert-manager manifests, add the following flag to the + cert-manager controller Deployment: + + ```yaml + args: + - --feature-gates=ExperimentalGatewayAPISupport=true + ``` + +The Gateway API CRDs should either be installed before cert-manager starts or +the cert-manager Deployment should be restarted after installing the Gateway API +CRDs. This is important because some of the cert-manager components only perform +the Gateway API check on startup. You can restart cert-manager with the +following command: + +```sh +kubectl rollout restart deployment cert-manager -n cert-manager +``` + +
+ + +
+ +🚧 cert-manager 1.8+ is tested with v1alpha2 Kubernetes Gateway API. It should also work +with v1beta1 because of resource conversion, but has not been tested with it. +
+ +The Gateway API HTTPRoute HTTP-01 solver creates a temporary HTTPRoute using the +given labels. These labels must match a Gateway that contains a listener on port +80. + +Here is an example of a HTTP-01 ACME Issuer using the Gateway API: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt + namespace: default +spec: + acme: + solvers: + - http01: + gatewayHTTPRoute: + parentRefs: + - name: traefik + namespace: traefik + kind: Gateway +``` + +The Issuer relies on an existing Gateway present on the cluster. cert-manager +does not edit Gateway resources. + +For example, the following Gateway will allow the Issuer to solve the challenge: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: Gateway +metadata: + name: traefik + namespace: traefik +spec: + gatewayClassName: traefik + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +``` + +In the above example, the Gateway has been specifically created for the purpose +of solving HTTP-01 challenges, but you can also choose to re-use your existing +Gateway, as long as it has a listener on port 80. + +The `labels` on your Issuer may reference a Gateway that is on a separate +namespace, as long as the Gateway's port 80 listener is configured with `from: +All`. Note that the Certificate will still be created on the same namespace as +the Issuer, which means that you won't be able to reference this Secret in the +above-mentioned Gateway. + +When the above Issuer is presented with a Certificate, cert-manager creates the +temporary HTTPRoute. For example, with the following Certificate: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-tls + namespace: default +spec: + issuerRef: + name: letsencrypt + dnsNames: + - example.net +``` + +You will see an HTTPRoute appear: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: HTTPRoute +metadata: + name: cm-acme-http-solver-gdhvg + namespace: default +spec: + parentRefs: + - name: traefik + namespace: traefik + kind: Gateway + hostnames: + - example.net + rules: + - forwardTo: + - port: 8089 + serviceName: cm-acme-http-solver-gdhvg + weight: 1 + matches: + - path: + type: Exact + value: /.well-known/acme-challenge/YadC4gaAzqEPU1Yea0D2MrzvNRWiBCtUizCtpiRQZqI +``` + +After the Certificate is issued, the HTTPRoute is deleted. + +

`labels`

+ +These labels are copied into the temporary HTTPRoute created by cert-manager for +solving the HTTP-01 challenge. These labels must match one of the Gateway +resources on your cluster. The matched Gateway have a listener on port 80. + +Note that when the labels do not match any Gateway on your cluster, cert-manager +will create the temporary HTTPRoute challenge and nothing will happen. + +

`serviceType`

+ +This field has the same meaning as the +[`http01.ingress.serviceType`](#ingress-service-type). + + +## Setting Nameservers for HTTP-01 solver propagation checks + +cert-manager will perform reachability tests before attempting a HTT01 +challenge. By default cert-manager will use the recursive nameservers taken +from `/etc/resolv.conf` to query the challenge URL. + +If this is not desired (for example with split-horizon DNS), the cert-manager +controller exposes a flag that allows you alter this behavior: + +`--acme-http01-solver-nameservers` Comma separated string with host and port of the +recursive nameservers cert-manager should query. + + +Example usage: +```bash +--acme-http01-solver-nameservers="8.8.8.8:53,1.1.1.1:53" +``` + +If you're using the `cert-manager` helm chart, you can set recursive nameservers +through `.Values.extraArgs` or at the command at helm install/upgrade time +with `--set`: + +```bash +--set 'extraArgs={--acme-http01-solver-nameservers=8.8.8.8:53\,1.1.1.1:53}' +``` diff --git a/content/v1.12-docs/configuration/acme/http01/externalloadbalancer.md b/content/v1.12-docs/configuration/acme/http01/externalloadbalancer.md new file mode 100644 index 0000000000..a85966bd29 --- /dev/null +++ b/content/v1.12-docs/configuration/acme/http01/externalloadbalancer.md @@ -0,0 +1,34 @@ +--- +title: External Load Balancer +description: 'cert-manager configuration: ACME HTTP-01 challenges using External Load Balancers' +--- + +When you are using an external load balancer provided by any host, you can face several configuration issues to get it work with cert-manager. + +This documentation is intended to help configure the HTTP-01 challenge type for instances behind external load balancer. + +## NAT Loopback / Hairpin + +The first configuration point is NAT loopback. You can face check issues due to Load Balancer preventing instances behind it to access its external interface. + +Some Network Load Balancer have this kind of limitation for several reasons. It can be configured through `iptables` rerouting configuration known as `NAT loopback`. + +To check if you are facing this problem : + +1. Check that the endpoint of the challenge is accessible to the public : `curl ` +2. Check that the challenge endpoint is NOT accessible from inside behind the Load Balancer: use SSH to open a session on a node places behind the LB; then launch the same command than before : `curl ` + +The `HTTP-01` challenge's endpoint can be found in the logs when the `pre-check` fails. If it does not appear in the logs, you can check the challenge URL by `kubectl`command. + +`` is the URL used to test the HTTP-01 from the certificate `Issuer`. For Let's Encrypt for example, the URL is formed like `/.well-known/acme-challenge/` + + +## Load Balancer HTTP endpoints + +If you are using a Load Balancer (outside a managed Kubernetes service), you should be able to configure the Load Balancer protocol as HTTP, HTTPS, TCP, UDP. Several Load Balancer now offer free TLS certificates with Let's Encrypt. + +When using HTTP(s) protocols for your Load Balancer, it can intercept the challenge URL to replace the response's verification hash with their hash. + +In this case, cert-manager will fail `did not get expected response when querying endpoint, expected 'xxxx' but got: yyyy (truncated)`. + +This kind of error can be thrown for multiple reasons. This case shows a correctly formatted response, but not the expected one. The solution is to configure the Load Balancer with TCP protocol so that the HTTP request will not be intercepted by the host. \ No newline at end of file diff --git a/content/v1.12-docs/configuration/ca.md b/content/v1.12-docs/configuration/ca.md new file mode 100644 index 0000000000..fffb6f2401 --- /dev/null +++ b/content/v1.12-docs/configuration/ca.md @@ -0,0 +1,94 @@ +--- +title: CA +description: 'cert-manager configuration: CA Issuers' +--- + +⚠️ CA issuers are generally either for trying cert-manager out or else for advanced users with +a good idea of how to run a PKI. To be used safely in production, CA issuers introduce complex +planning requirements around rotation, trust store distribution and disaster recovery. + +If you're not planning to run your own PKI, use a different issuer type. + +The CA issuer represents a Certificate Authority whose certificate and +private key are stored inside the cluster as a Kubernetes `Secret`. + +Certificates issued by a CA issuer will not be publicly trusted and so are unlikely to be trusted +by your applications without further configuration. + +Consider [trust-manager](../projects/trust-manager/README.md) for distributing your CA certificate safely +across your cluster! + +## Deployment + +CA Issuers must be configured with a certificate and private key stored in a Kubernetes +secret. You can create this externally if you wish, or you could bootstrap a root certificate +using a [`SelfSigned` issuer](./selfsigned.md#bootstrapping-ca-issuers). + +Your certificate's secret should reside in the same namespace as the `Issuer`, or otherwise +in the `Cluster Resource Namespace` in the case of a `ClusterIssuer`. + +The `Cluster Resource Namespace` is defaulted as being the `cert-manager` namespace, but +can be configured using the `--cluster-resource-namespace` flag on the cert-manager controller. + +Below is an example of a secret resource that will be used for signing. Take +note of the index keys used for each field as these are required in order for +cert-manager to find the certificate and key. Also note that, like all secrets, +data must be base64 encoded. The command `$ cat crt.pem | base64 -w0` should help you +on GNU-based systems (Debian, Ubuntu, etc.) and `$ cat crt.pem | base64 -b0` on BSD-based +systems (most notably macOS). + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: ca-key-pair + namespace: sandbox +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMrVENDQWVHZ0F3SUJBZ0lKQUtQR3dLRGwvNUhuTUEwR0NTcUdTSWIzRFFFQkN3VUFNQk14RVRBUEJnTlYKQkFNTUNHcHZjMmgyWVc1c01CNFhEVEU1TURneU1qRTJNRFUxT0ZvWERUSTVNRGd4T1RFMk1EVTFPRm93RXpFUgpNQThHQTFVRUF3d0lhbTl6YUhaaGJtd3dnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCCkFRQ3doU0IvcVc2L2tMYjJ6cHUrRUp2RDl3SEZhcStRQS8wSkgvTGxseW83ekFGeCtISHErQ09BYmsrQzhCNHQKL0hVRXNuczVSTDA5Q1orWDRqNnBiSkZkS2R1UHhYdTVaVllua3hZcFVEVTd5ZzdPU0tTWnpUbklaNzIzc01zMApSNmpZbi9Ecmo0eFhNSkVmSFVEcVllU1dsWnIzcWkxRUZhMGM3ZlZEeEgrNHh0WnROTkZPakg3YzZEL3ZXa0lnCldRVXhpd3Vzc2U2S01PV2pEbnYvNFZyamVsMlFnVVlVYkhDeWVaSG1jdGkrSzBMV0Nmby9SZzZQdWx3cmJEa2gKam1PZ1l0MzBwZGhYME9aa0F1a2xmVURIZnA4YmpiQ29JMnRhWUFCQTZBS2pLc08zNUxBRVU3OUNMMW1MVkh1WgpBQ0k1VWppamEzVlBXVkhTd21KUEp5dXhBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlFtbDVkVEFaaXhGS2hqCjkzd3VjUldoYW8vdFFqQWZCZ05WSFNNRUdEQVdnQlFtbDVkVEFaaXhGS2hqOTN3dWNSV2hhby90UWpBTUJnTlYKSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFCK2tsa1JOSlVLQkxYOHlZa3l1VTJSSGNCdgpHaG1tRGpKSXNPSkhac29ZWGRMbEcxcFpORmpqUGFPTDh2aDQ0Vmw5OFJoRVpCSHNMVDFLTWJwMXN1NkNxajByClVHMWtwUkJlZitJT01UNE1VN3ZSSUNpN1VPbFJMcDFXcDBGOGxhM2hQT2NSYjJ5T2ZGcVhYeVpXWGY0dDBCNDUKdEhpK1pDTkhCOUZ4alNSeWNiR1lWaytUS3B2aEphU1lOTUdKM2R4REthUDcrRHgzWGNLNnNBbklBa2h5SThhagpOVSttdzgvdG1Sa1A0SW4va1hBUitSaTBxVW1Iai92d3ZuazRLbTdaVXkxRllIOERNZVM1TmtzbisvdUhsUnhSClY3RG5uMDM5VFJtZ0tiQXFONzJnS05MbzVjWit5L1lxREFZSFlybjk4U1FUOUpEZ3RJL0svQVRwVzhkWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBc0lVZ2Y2bHV2NUMyOXM2YnZoQ2J3L2NCeFdxdmtBUDlDUi95NVpjcU84d0JjZmh4CjZ2Z2pnRzVQZ3ZBZUxmeDFCTEo3T1VTOVBRbWZsK0krcVd5UlhTbmJqOFY3dVdWV0o1TVdLVkExTzhvT3praWsKbWMwNXlHZTl0N0RMTkVlbzJKL3c2NCtNVnpDUkh4MUE2bUhrbHBXYTk2b3RSQld0SE8zMVE4Ui91TWJXYlRUUgpUb3grM09nLzcxcENJRmtGTVlzTHJMSHVpakRsb3c1Ny8rRmE0M3Bka0lGR0ZHeHdzbm1SNW5MWXZpdEMxZ242ClAwWU9qN3BjSzJ3NUlZNWpvR0xkOUtYWVY5RG1aQUxwSlgxQXgzNmZHNDJ3cUNOcldtQUFRT2dDb3lyRHQrU3cKQkZPL1FpOVppMVI3bVFBaU9WSTRvMnQxVDFsUjBzSmlUeWNyc1FJREFRQUJBb0lCQUNFTkhET3JGdGg1a1RpUApJT3dxa2UvVVhSbUl5MHlNNHFFRndXWXBzcmUxa0FPMkFDWjl4YS96ZDZITnNlanNYMEM4NW9PbmtrTk9mUHBrClcxVS94Y3dLM1ZpRElwSnBIZ09VNzg1V2ZWRXZtU3dZdi9Fb1V3eHFHRVMvcnB5Z1drWU5WSC9XeGZGQlg3clMKc0dmeVltbXJvM09DQXEyLzNVVVFiUjcrT09md3kzSHdUdTBRdW5FSnBFbWU2RXdzdWIwZzhTTGp2cEpjSHZTbQpPQlNKSXJyL1RjcFRITjVPc1h1Vm5FTlVqV3BBUmRQT1NrRFZHbWtCbnkyaVZURElST3NGbmV1RUZ1NitXOWpqCmhlb1hNN2czbkE0NmlLenUzR0YwRWhLOFkzWjRmeE42NERkbWNBWnphaU1vMFJVaktWTFVqbVlQSEUxWWZVK3AKMkNYb3dNRUNnWUVBMTgyaU52UEkwVVlWaUh5blhKclNzd1YrcTlTRStvVi90U2ZSUUNGU2xsV0d3KzYyblRiVwpvNXpoL1RDQW9VTVNSbUFPZ0xKWU1LZUZ1SWdvTEoxN1pvWjN0U1czTlVtMmRpT0lPSHorcTQxQzM5MDRrUzM5CjkrYkFtVmtaSFA5VktLOEMraS9tek5mSkdHZEJadGIweWtTM2t3OUIxTHdnT3o3MDhFeXFSQ2tDZ1lFQTBXWlAKbzF2MThnV2tMK2FnUDFvOE13eDRPZlpTN3dKY3E0Z0xnUWhjYS9pSkttY0x0RFN4cUJHckJ4UVo0WTIyazlzdQpzTFVrNEJobGlVM29iUUJNaUdtMGtITHVBSEFRNmJvdWZBMUJwZjN2VFdHSkhSRjRMeFJsNzc2akw4UXI4VnpxClpURVBtY0R0T0hpYjdwb2I1Z2IzSDhiVGhYeUhmdGZxRW55alhFa0NnWUVBdk9DdDZZclZhTlQrWThjMmRFYk4Kd3dJOExBaUZtdjdkRjZFUjlCODJPWDRCeGR0WTJhRDFtNTNqN2NaVnpzNzFYOE1TN25FcDN1dkFqaElkbDI3KwpZbTJ1dUUyYVhIbDN5VTZ3RzBETFpUcnVIU0Z5TVI4ZithbHRTTXBDd0s1NXluSGpHVFp6dXpYaVBBbWpwRzdmCk1XbVRncE1IK3puc3UrNE9VNFBHUW9FQ2dZQWNqdUdKbS84YzlOd0JsR2lDZTJIK2JGTHhSTURteStHcm16QkcKZHNkMENqOWF3eGI3aXJ3MytjRGpoRUJMWExKcjA5YTRUdHdxbStrdElxenlRTG92V0l0QnNBcjVrRThlTVVBcAp0djBmRUZUVXJ0cXVWaldYNWlaSTNpMFBWS2ZSa1NSK2pJUmVLY3V3aWZKcVJpWkw1dU5KT0NxYzUvRHF3Yk93CnRjTHAwUUtCZ0VwdEw1SU10Sk5EQnBXbllmN0F5QVBhc0RWRE9aTEhNUGRpL2dvNitjSmdpUmtMYWt3eUpjV3IKU25QSG1TbFE0aEluNGMrNW1lbHBDWFdJaklLRCtjcTlxT2xmQmRtaWtYb2RVQ2pqWUJjNnVGQ1QrNWRkMWM4RwpiUkJQOUNtWk9GL0hOcHN0MEgxenhNd1crUHk5Q2VnR3hhZ0ZCekxzVW84N0xWR2h0VFFZCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== +``` + +> Note: If your issuer represents an intermediate, ensure that `tls.crt` contains +> the issuer's full chain in the correct order: `issuer -> intermediate(s) -> root`. +> The root (self-signed) CA certificate is optional, but adding it will ensure that +> the correct CA certificate is stored in the secrets for issued `Certificate`s under +> the `ca.crt` key. If you fail to provide a complete chain, it might not be possible +> for consumers of issued `Certificate`s to verify whether they're trusted. + +Next is to deploy the CA issuer which references this `Secret`. This is done by +referencing the secret name under the `ca` stanza in the `Issuer` spec. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ca-issuer + namespace: sandbox +spec: + ca: + secretName: ca-key-pair +``` + +Optionally, you can specify [CRL](https://en.wikipedia.org/wiki/Certificate_revocation_list) Distribution Points; an array of strings each of which identifies the location of the CRL from which the revocation of this certificate can be checked. + +```yaml +... +spec: + ca: + secretName: ca-key-pair + crlDistributionPoints: + - "http://example.com" +``` + +Once deployed, you can then check that the issuer has been successfully +configured by checking the ready status of the certificate. Replace `issuers` +here with `clusterissuers` if that is what has been deployed. + +```bash +$ kubectl get issuers ca-issuer -n sandbox -o wide +NAME READY STATUS AGE +ca-issuer True Signing CA verified 2m +``` + +Certificates are now ready to be requested by using the CA `Issuer` named +`ca-issuer` within the `sandbox` namespace. diff --git a/content/v1.12-docs/configuration/external.md b/content/v1.12-docs/configuration/external.md new file mode 100644 index 0000000000..d4bc78088f --- /dev/null +++ b/content/v1.12-docs/configuration/external.md @@ -0,0 +1,51 @@ +--- +title: External +description: 'cert-manager configuration: External Issuers' +--- + +cert-manager supports external `Issuer` types. While external issuers are not +implemented in the main cert-manager repository, they are otherwise treated the +same as any other issuer. + +External issuers are typically deployed as a pod which is configured +to watch for `CertificateRequest` resources in the cluster whose `issuerRef` +matches the name of the issuer. External issuers exist outside of the +`cert-manager.io` group. + +Installation for each issuer may differ; check the documentation for each +external issuer for more details on installing, configuring and using it. + +## Known External Issuers + +If you've created an external issuer which you'd like to share, +[raise a Pull Request](https://github.com/cert-manager/website/pulls) to have +it added here! + +These external issuers are known to support and honor [approval](https://cert-manager.io/docs/concepts/certificaterequest/#approval). + +- [kms-issuer](https://github.com/Skyscanner/kms-issuer): Requests + certificates signed using an [AWS KMS](https://aws.amazon.com/kms/) asymmetric key. +- [aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer): Requests + certificates from [AWS Private Certificate Authority](https://aws.amazon.com/certificate-manager/private-certificate-authority/) + for cloud native/hybrid environments. +- [google-cas-issuer](https://github.com/jetstack/google-cas-issuer): Used + to request certificates signed by private CAs managed by the + [Google Cloud Certificate Authority Service](https://cloud.google.com/certificate-authority-service/). +- [origin-ca-issuer](https://github.com/cloudflare/origin-ca-issuer): Used + to request certificates signed by + [Cloudflare Origin CA](https://developers.cloudflare.com/ssl/origin-configuration/origin-ca) + to enable TLS between Cloudflare edge and your Kubernetes workloads. +- [step-issuer](https://github.com/smallstep/step-issuer): Requests + certificates from the [Smallstep](https://smallstep.com) [Certificate Authority server](https://github.com/smallstep/certificates). +- [freeipa-issuer](https://github.com/guilhem/freeipa-issuer): Requests + certificates signed by [FreeIPA](https://www.freeipa.org). +- [ADCS Issuer](https://github.com/nokia/adcs-issuer): Requests + certificates signed by [Microsoft Active Directory Certificate Service](https://docs.microsoft.com/en-us/windows-server/networking/core-network-guide/cncg/server-certs/install-the-certification-authority). + [NOT MAINTAINED] +- [CFSSL Issuer](https://gerrit.wikimedia.org/r/plugins/gitiles/operations/software/cfssl-issuer/): Request certificates signed by a [CFSSL](https://github.com/cloudflare/cfssl) `multirootca` instance. +- [ncm-issuer](https://github.com/nokia/ncm-issuer): Requests certificates from the [Nokia](https://www.nokia.com/) [Netguard Certificate Manager](https://www.nokia.com/networks/security-portfolio/netguard/certificate-manager) +- [tcs-issuer](https://github.com/intel/trusted-certificate-issuer) Requests certificates signed securely using [Intel's SGX technology](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/overview.html). + +## Building New External Issuers + +If you're interested in building a new external issuer, check the [development documentation](../contributing/external-issuers.md). diff --git a/content/v1.12-docs/configuration/selfsigned.md b/content/v1.12-docs/configuration/selfsigned.md new file mode 100644 index 0000000000..bfc2b39b2b --- /dev/null +++ b/content/v1.12-docs/configuration/selfsigned.md @@ -0,0 +1,171 @@ +--- +title: SelfSigned +description: 'cert-manager configuration: SelfSigned Issuers' +--- + +⚠️ `SelfSigned` issuers are generally useful for bootstrapping a PKI locally, which +is a complex topic for advanced users. To be used safely in production, running a PKI +introduces complex planning requirements around rotation, trust store distribution and disaster recovery. + +If you're not planning to run your own PKI, use a different issuer type. + +The `SelfSigned` issuer doesn't represent a certificate authority as such, but +instead denotes that certificates will "sign themselves" using a given private +key. In other words, the private key of the certificate will be used to sign +the certificate itself. + +This `Issuer` type is useful for bootstrapping a root certificate for a +custom PKI (Public Key Infrastructure), or for otherwise creating simple +ad-hoc certificates for a quick test. + +There are important [caveats](#caveats) - including security issues - to +consider with `SelfSigned` issuers; in general you'd likely want to use a +[`CA`](./ca.md) issuer rather than a `SelfSigned` issuer. That said, +`SelfSigned` issuers are really useful for initially [bootstrapping](#bootstrapping-ca-issuers) +a `CA` issuer. + +> Note: a `CertificateRequest` that references a self-signed certificate _must_ +> also contain the `cert-manager.io/private-key-secret-name` annotation since +> the private key corresponding to the `CertificateRequest` is required to +> sign the certificate. This annotation is added automatically by the +> `Certificate` controller. + +## Deployment + +Since the `SelfSigned` issuer has no dependency on any other resource, it is +the simplest to configure. Only the `SelfSigned` stanza is required to be +present in the issuer spec, with no other configuration required: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: sandbox +spec: + selfSigned: {} +``` + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-cluster-issuer +spec: + selfSigned: {} +``` + +Once deployed, you should be able to see immediately that the issuer is ready +for signing: + +```bash +$ kubectl get issuers -n sandbox -o wide selfsigned-issuer +NAME READY STATUS AGE +selfsigned-issuer True 2m + +$ kubectl get clusterissuers -o wide selfsigned-cluster-issuer +NAME READY STATUS AGE +selfsigned-cluster-issuer True 3m +``` + +### Bootstrapping `CA` Issuers + +One of the ideal use cases for `SelfSigned` issuers is to bootstrap a custom +root certificate for a private PKI, including with the cert-manager [`CA`](./ca.md) +issuer. + +The YAML below will create a `SelfSigned` issuer, issue a root certificate and +use that root as a `CA` issuer: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: sandbox +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: my-selfsigned-ca + namespace: sandbox +spec: + isCA: true + commonName: my-selfsigned-ca + secretName: root-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + group: cert-manager.io +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: my-ca-issuer + namespace: sandbox +spec: + ca: + secretName: root-secret +``` + +### CRL Distribution Points + +You may also optionally specify [CRL](https://en.wikipedia.org/wiki/Certificate_revocation_list) +Distribution Points as an array of strings, each of which identifies the location of a CRL in +which the revocation status of issued certificates can be checked: + +```yaml +... +spec: + selfSigned: + crlDistributionPoints: + - "http://example.com" +``` + +## Caveats + +### Trust + +Clients consuming `SelfSigned` certificates have _no way_ to trust them +without already having the certificates beforehand, which can be hard to +manage when the client is in a different namespace to the server. + +This limitation can be tackled by using [trust-manager](../projects/trust-manager/README.md) to distribute `ca.crt` +to other namespaces. + +There is no secure alternative to solving the problem of distributing trust stores; it's possible +to "TOFU" (trust-on-first-use) a certificate, but that approach is vulnerable to man-in-the-middle attacks. + +### Certificate Validity + +One side-effect of a certificate being self-signed is that its Subject DN and +its Issuer DN are identical. The X.509 [RFC 5280, section 4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4) +requires that: + +> The issuer field MUST contain a non-empty distinguished name (DN). + +However, self-signed certs don't have a subject DN set by default. Unless you +manually set a certificate's Subject DN, the Issuer DN will be empty +and the certificate will technically be invalid. + +Validation of this specific area of the spec is patchy and varies between TLS +libraries, but there's always the risk that a library will improve its +validation - entirely within spec - in the future and break your app if you're +using a certificate with an empty Issuer DN. + +To avoid this, be sure to set a Subject for `SelfSigned` certs. This can be +done by setting the `spec.subject` on a cert-manager `Certificate` object +which will be issued by a `SelfSigned` issuer. + +Starting in version 1.3, cert-manager will emit a Kubernetes [warning event](https://github.com/cert-manager/cert-manager/blob/45befd86966c563663d18848943a1066d9681bf8/pkg/controller/certificaterequests/selfsigned/selfsigned.go#L140) +of type `BadConfig` if it detects that a certificate is being created +by a `SelfSigned` issuer which has an empty Issuer DN. diff --git a/content/v1.12-docs/configuration/vault.md b/content/v1.12-docs/configuration/vault.md new file mode 100644 index 0000000000..c2dd25aee5 --- /dev/null +++ b/content/v1.12-docs/configuration/vault.md @@ -0,0 +1,270 @@ +--- +title: Vault +description: 'cert-manager configuration: Vault Issuers' +--- + +The `Vault` `Issuer` represents the certificate authority +[Vault](https://www.vaultproject.io/) - a multi-purpose secret store that can be +used to sign certificates for your Public Key Infrastructure (PKI). Vault is an +external project to cert-manager and as such, this guide will assume it has been +configured and deployed correctly, ready for signing. You can read more on how +to configure Vault as a certificate authority +[here](https://www.vaultproject.io/docs/secrets/pki/). + +This `Issuer` type is typically used when Vault is already being used within +your infrastructure, or you would like to make use of its feature set where the +CA issuer alone cannot provide. + +## Deployment + +All Vault issuers share common configuration for requesting certificates, +namely the server, path, and CA bundle: + +- Server is the URL whereby Vault is reachable. +- Path is the Vault path that will be used for signing. Note that the path + *must* use the `sign` endpoint. +- CA bundle denotes an optional field containing a base64 encoded string of the + Certificate Authority to trust the Vault connection. This is typically + _always_ required when using an `https` URL. + +Below is an example of a configuration to connect a Vault server. + +> **Warning**: This configuration is incomplete as no authentication methods have +> been added. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: vault-issuer + namespace: sandbox +spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + ... +``` + +## Authenticating + +In order to request signing of certificates by Vault, the issuer must be able to +properly authenticate against it. cert-manager provides multiple approaches to +authenticating to Vault which are detailed below. + +### Authenticating via an AppRole + +An [AppRole](https://www.vaultproject.io/docs/auth/approle.html) is a method of +authenticating to Vault through use of its internal role policy system. This +authentication method requires that the issuer has possession of the `SecretID` +secret key, the `RoleID` of the role to assume, and the app role path. Firstly, +the secret ID key must be stored within a Kubernetes `Secret` that resides in the +same namespace as the `Issuer`, or otherwise inside the `Cluster Resource +Namespace` in the case of a `ClusterIssuer`. + +```yaml +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: cert-manager-vault-approle + namespace: sandbox +data: + secretId: "MDI..." +``` + +Once the `Secret` has been created, the `Issuer` is ready to be deployed which +references this `Secret`, as well as the data key of the field that stores the +secret ID. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: vault-issuer + namespace: sandbox +spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + appRole: + path: approle + roleId: "291b9d21-8ff5-..." + secretRef: + name: cert-manager-vault-approle + key: secretId +``` + +### Authenticating with a Token + +This method of authentication uses a token string that has been generated from +one of the many authentication backends that Vault supports. These tokens have +an expiry and so need to be periodically refreshed. You can read more on Vault +tokens [here](https://www.vaultproject.io/docs/concepts/tokens.html). + +> **Note**: cert-manager does not refresh these token automatically and so another +> process must be put in place to do this. + +Firstly, the token is be stored inside a Kubernetes `Secret` inside the same +namespace as the `Issuer` or otherwise in the `Cluster Resource Namespace` in +the case of using a `ClusterIssuer`. + +```yaml +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: cert-manager-vault-token + namespace: sandbox +data: + token: "MjI..." +``` + +Once submitted, the Vault issuer is able to be created using token +authentication by referencing this `Secret` along with the key of the field the +token data is stored at. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: vault-issuer + namespace: sandbox +spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + tokenSecretRef: + name: cert-manager-vault-token + key: token +``` + +### Authenticating with Kubernetes Service Accounts + +Vault can be configured so that applications can authenticate using Kubernetes +[`Service Account +Tokens`](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin). +You find documentation on how to configure Vault to authenticate using Service +Account Tokens [here](https://www.vaultproject.io/docs/auth/kubernetes.html). + +For the Vault issuer to use this authentication, cert-manager must get access to +the token that is stored in a Kubernetes `Secret`. Kubernetes Service Account +Tokens are already stored in `Secret` resources however, you must ensure that +it is present in the same namespace as the `Issuer`, or otherwise in the +`Cluster Resource Namespace` in the case of using a `ClusterIssuer`. + +> **Note**: In Kubernetes 1.24 onwards, the token secret is no longer created +> by default for the Service Account. In this case you need to manually create +> the secret resource. See [this guide](https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets) +> for more details. + +This authentication method also expects a `role` field which is the Vault role +that the Service Account is to assume, as well as an optional `mountPath` field which +is the authentication mount path, defaulting to `kubernetes`. + +#### Kubernetes version less than 1.24 + +The following example will be making use of the Service Account +`my-service-account`. The secret data field key will be `token` if the `Secret` +has been created by Kubernetes. The Vault role used is `my-app-1`, using the +default mount path of `/v1/auth/kubernetes` + +1) Create the Service Account: + + ```shell + kubectl create serviceaccount -n sandbox vault-issuer + ``` + +1) Get the auto-generated Secret name: + + ```shell + kubectl get secret -o json | jq -r '.items[] | select(.metadata.annotations["kubernetes.io/service-account.name"] == "vault-issuer") | .metadata.name' + ``` + +1) Create the Issuer using that Secret name retrieved from the previous step: + + ```yaml + apiVersion: cert-manager.io/v1 + kind: Issuer + metadata: + name: vault-issuer + namespace: sandbox + spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + kubernetes: + role: my-app-1 + mountPath: /v1/auth/kubernetes + secretRef: + name: + key: token + ``` + +#### Kubernetes version 1.24 and greater + +This example is almost the same as above but adjusted for the change in +Kubernetes 1.24 and above. + +1) Create the Service Account: + + ```shell + kubectl create serviceaccount -n sandbox vault-issuer + ``` + +1) Create the Secret resource for Kubernetes to populate the `token` value: + + ```yaml + apiVersion: v1 + kind: Secret + metadata: + name: vault-issuer-token + annotations: + kubernetes.io/service-account.name: "vault-issuer" + type: kubernetes.io/service-account-token + data: {} + ``` + +1) Create the Issuer resource referencing the Secret resource: + + ```yaml + apiVersion: cert-manager.io/v1 + kind: Issuer + metadata: + name: vault-issuer + namespace: sandbox + spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + kubernetes: + role: my-app-1 + mountPath: /v1/auth/kubernetes + secretRef: + name: vault-issuer-token + key: token + ``` + +## Verifying the issuer Deployment + +Once the Vault issuer has been deployed, it will be marked as ready if the +configuration is valid. Replace `issuers` here with `clusterissuers` if that is what has +been deployed. + +```bash +$ kubectl get issuers vault-issuer -n sandbox -o wide +NAME READY STATUS AGE +vault-issuer True Vault verified 2m +``` + +Certificates are now ready to be requested by using the Vault issuer named +`vault-issuer` within the `sandbox` namespace. \ No newline at end of file diff --git a/content/v1.12-docs/configuration/venafi.md b/content/v1.12-docs/configuration/venafi.md new file mode 100644 index 0000000000..93498b0ace --- /dev/null +++ b/content/v1.12-docs/configuration/venafi.md @@ -0,0 +1,282 @@ +--- +title: Venafi +description: 'cert-manager configuration: Venafi Issuers' +--- + +## Introduction + +The Venafi `Issuer` types allows you to obtain certificates from [Venafi +as a Service (VaaS)](https://vaas.venafi.com/jetstack) and [Venafi Trust Protection +Platform (TPP)](https://www.venafi.com/platform/tls-protect) instances. + +You can have multiple different Venafi `Issuer` types installed within the same +cluster, including mixtures of Venafi as a Service and TPP issuer types. This allows +you to be flexible with the types of Venafi account you use. + +Automated certificate renewal and management are provided for `Certificates` +using the Venafi `Issuer`. + +A single Venafi `Issuer` represents a single Venafi 'zone' so you must create one +`Issuer` resource for each zone you want to use. A zone is a single entity that +combines the policy that governs certificate issuance with information about how +certificates are organized in Venafi to identify the business application and +establish ownership. + +You can configure your `Issuer` resource to either issue certificates only +within a single namespace, or cluster-wide (using a `ClusterIssuer` resource). +For more information on the distinction between `Issuer` and `ClusterIssuer` +resources, read the [Namespaces](../concepts/issuer.md#namespaces) section. + +## Creating a Venafi as a Service Issuer + +If you haven't already done so, create your Venafi as a Service account on this +[page](https://vaas.venafi.com/jetstack) and copy the API key from your user +preferences. Then you may want to create a custom CA Account and Issuing Template +or choose instead to use defaults that are automatically created for testing +("Built-in CA" and "Default", respectively). Lastly you'll need to create an +Application for establishing ownership of all the certificates requested by your +cert-manager Issuer, and assign to it the Issuing Template. + +> Make a note of the Application name and API alias of the Issuing Template because +> together they comprise the 'zone' you will need for your `Issuer` configuration. + +In order to set up a Venafi as a Service `Issuer`, you must first create a Kubernetes +`Secret` resource containing your Venafi as a Service API credentials: + +```bash +$ kubectl create secret generic \ + vaas-secret \ + --namespace='NAMESPACE OF YOUR ISSUER RESOURCE' \ + --from-literal=apikey='YOUR_VAAS_API_KEY_HERE' +``` + +> **Note**: If you are configuring your issuer as a `ClusterIssuer` resource in +> order to serve `Certificates` across your whole cluster, you must set the +> `--namespace` parameter to `cert-manager`, which is the default `Cluster +> Resource Namespace`. The `Cluster Resource Namespace` can be configured +> through the `--cluster-resource-namespace` flag on the cert-manager controller +> component. + +This API key will be used by cert-manager to interact with Venafi as a Service +on your behalf. + +Once the API key `Secret` has been created, you can create your `Issuer` or +`ClusterIssuer` resource. If you are creating a `ClusterIssuer` resource, you +must change the `kind` field to `ClusterIssuer` and remove the +`metadata.namespace` field. + +Save the below content after making your amendments to a file named +`vaas-issuer.yaml`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: vaas-issuer + namespace: +spec: + venafi: + zone: "My Application\My CIT" # Set this to \ + cloud: + apiTokenSecretRef: + name: vaas-secret + key: apikey +``` + +You can then create the Issuer using `kubectl create`. + +```bash +$ kubectl create -f vaas-issuer.yaml +``` + +Verify the `Issuer` has been initialized correctly using `kubectl describe`. + +```bash +$ kubectl get issuer vaas-issuer --namespace='NAMESPACE OF YOUR ISSUER RESOURCE' -o wide +NAME READY STATUS AGE +vaas-issuer True Venafi issuer started 2m +``` + +You are now ready to issue certificates using the newly provisioned Venafi +`Issuer` and Venafi as a Service. + +Read the [Issuing Certificates](../usage/certificate.md) document for +more information on how to create Certificate resources. + + +## Creating a Venafi Trust Protection Platform Issuer + +The Venafi Trust Protection Platform integration allows you to obtain certificates +from a properly configured Venafi TPP instance. + +The setup is similar to the Venafi as a Service configuration above, however some +of the connection parameters are slightly different. + +> **Note**: You *must* allow "User Provided CSRs" as part of your TPP policy, as +> this is the only type supported by cert-manager at this time. +> +> More specifically, the valid configurations of the "CSR handling" are: +> +> - "User Provided CSRs" selected and unlocked, +> - "User Provided CSRs" selected and locked, +> - "Service Generated CSRs" selected and unlocked. +> +> When using "Service Generated CSRs" selected and unlocked, the default CSR +> configuration present in your policy folder will override the configuration of +> your Certificate resource. The subject DN, key algorithm, and key size will be +> overridden by the values set in the policy folder. +> +> With "Service Generated CSRs" selected and locked, the certificate issuance +> will systematically fail with the following message: +> +> ```plain +> 400 PKCS#10 data will not be processed. Policy "\VED\Policy\foo" is locked to a Server Generated CSR. +> ``` + +In order to set up a Venafi Trust Protection Platform `Issuer`, you must first +create a Kubernetes `Secret` resource containing your Venafi TPP API +credentials. + +### Access Token Authentication + +1. [Set up token authentication](https://docs.venafi.com/Docs/21.1/TopNav/Content/SDK/AuthSDK/t-SDKa-Setup-OAuth.php). + + NOTE: Do not select "Refresh Token Enabled" and set a *long* "Token Validity (days)". + +2. Create a new user with sufficient privileges to manage and revoke certificates in a particular policy folder (zone). + + E.g. `k8s-xyz-automation` + +3. [Create a new application integration](https://docs.venafi.com/Docs/21.1/TopNav/Content/API-ApplicationIntegration/t-APIAppIntegrations-creatingNew-Aperture.php) + + Create an application integration with name and ID `cert-manager`. + Set the "API Access Settings" to `Certificates: Read,Manage,Revoke`. + + "Edit Access" to the new application integration, and allow it to be used by the user you created earlier. + +4. [Generate an access token](https://github.com/Venafi/vcert/blob/master/README-CLI-PLATFORM.md#obtaining-an-authorization-token) + + ``` + vcert getcred \ + --username k8s-xyz-automation \ + --password somepassword \ + -u https://tpp.example.com/vedsdk \ + --client-id cert-manager \ + --scope "certificate:manage,revoke" + ``` + + This will print an access-token to `stdout`. E.g. + + ``` + vCert: 2020/10/07 16:34:27 Getting credentials + access_token: I69n.............y1VjNJT3o9U0Wko19g== + access_token_expires: 2021-01-05T15:34:30Z + ``` + +5. Save the access-token to a Secret in the Kubernetes cluster + +```bash +$ kubectl create secret generic \ + tpp-secret \ + --namespace= \ + --from-literal=access-token='YOUR_TPP_ACCESS_TOKEN' +``` + +### Username / Password Authentication + +> ⚠️ When you supply a Venafi TPP username and password, +> cert-manager uses an older authentication method which is called "API Keys", +> which has been deprecated since Venafi TPP `19.2`. +> +> Beginning in Venafi TPP `22.2`, "API Keys" are disabled by default. +> You will need to contact Venafi customer support for a special license key which will allow you to re-enable the "API Keys" feature, +> so that you can continue to use username and password authentication with cert-manager. +> +> In Venafi TPP `22.3`, the "API Keys" feature will be permanently removed, +> and you will need to use access-token authentication instead. +> +> 📖 Read [Deprecated functionality from Venafi Platform](https://docs.venafi.com/22.3/deprecation-list-current) +> and [Functionality Scheduled for Deprecation](https://support.venafi.com/hc/en-us/articles/115001662292) for more information. + +```bash +$ kubectl create secret generic \ + tpp-secret \ + --namespace= \ + --from-literal=username='YOUR_TPP_USERNAME_HERE' \ + --from-literal=password='YOUR_TPP_PASSWORD_HERE' +``` + +> Note: If you are configuring your issuer as a `ClusterIssuer` resource in +> order to issue `Certificates` across your whole cluster, you must set the +> `--namespace` parameter to `cert-manager`, which is the default `Cluster +> Resource Namespace`. The `Cluster Resource Namespace` can be configured +> through the `--cluster-resource-namespace` flag on the cert-manager controller +> component. + +These credentials will be used by cert-manager to interact with your Venafi TPP +instance. Username attribute must be adhere to the `:` format. For example: `local:admin`. + +Once the Secret containing credentials has been created, you can create your +`Issuer` or `ClusterIssuer` resource. If you are creating a `ClusterIssuer` +resource, you must change the `kind` field to `ClusterIssuer` and remove the +`metadata.namespace` field. + +Save the below content after making your amendments to a file named +`tpp-issuer.yaml`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: tpp-issuer + namespace: +spec: + venafi: + zone: \VED\Policy\devops\cert-manager # Set this to the Venafi policy folder you want to use + tpp: + url: https://tpp.venafi.example/vedsdk # Change this to the URL of your TPP instance + caBundle: + credentialsRef: + name: tpp-secret +``` + +You can then create the `Issuer` using `kubectl create -f`. + +```bash +$ kubectl create -f tpp-issuer.yaml +``` + +Verify the `Issuer` has been initialized correctly using `kubectl describe`. + +```bash +$ kubectl describe issuer tpp-issuer --namespace='NAMESPACE OF YOUR ISSUER RESOURCE' +``` + +You are now ready to issue certificates using the newly provisioned Venafi +`Issuer` and Trust Protection Platform. + +Read the [Issuing Certificates](../usage/certificate.md) document for +more information on how to create Certificate resources. + +## Issuer specific annotations + +### Custom Fields + +Starting `v0.14` you can pass custom fields to Venafi (TPP version `v19.2` and higher) using the `venafi.cert-manager.io/custom-fields` annotation on Certificate resources. +The value is a JSON encoded array of custom field objects having a `name` and `value` key. +For example: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-certificate + annotations: + venafi.cert-manager.io/custom-fields: |- + [ + {"name": "field-name", "value": "field value"}, + {"name": "field-name-2", "value": "field value 2"} + ] +... +``` diff --git a/content/v1.12-docs/contributing/README.md b/content/v1.12-docs/contributing/README.md new file mode 100644 index 0000000000..aec6b21254 --- /dev/null +++ b/content/v1.12-docs/contributing/README.md @@ -0,0 +1,68 @@ +--- +title: Contributing +description: 'cert-manager contributing guide: Get involved!' +--- + +## Great to See You! + +Whether you're a previous contributor or a first timer looking to get involved, we love +it when the community comes together to improve the project! + +In this "contributing" section we document processes we follow as a project, and include +some details on how to build, test and run cert-manager for development purposes. + +## Meetings + +All cert-manager meetings are open for everyone to join; if you have a question or a suggestion or just want to chat, +please feel free to come along and get involved! + +To get invites you can subscribe to [our mailing list](https://groups.google.com/forum/#!forum/cert-manager-dev) and +you should receive calendar invites by mail shortly after joining. The complexities of calendars mean that some invites +might be sent multiple times depending on your email and calendar providers and you might get some invites for past +or future meetings which have been rescheduled or edited. Sorry about that! + +We have 2 regular repeating meetings: our quick daily check-in and an hour-long community meeting every two weeks. + +If you're having any issues joining our meetings, ensure that you're part of the [`cert-manager-dev`](https://groups.google.com/forum/#!forum/cert-manager-dev) Google group, and always feel free to ask in [Slack](./#slack) for help! + +
+🔰 All of our meetings happen on London (UK) time; you can add London to the world clocks on your phone to avoid confusion! + +When daylight savings time changes in London might be different to when it changes for you if you live in a place that either +doesn't have DST or which changes on a different schedule like North America or Australia! +
+ +### Daily Check-In + +Our daily check-in meetings [happen on Google Meet](https://meet.google.com/eum-fyvt-xpa) at [10:30 London time](http://www.thetimezoneconverter.com/?t=10:30&tz=Europe/London) every weekday. + +The format is a 5 minute social chat, followed by a quick round-robin status report and ending with any longer form talking points. + +The status report is a stand-up where we talk about work done yesterday, work coming up and highlight any blockers. +We'll try to keep to a **strict time limit** during these status reports of around 1 minute per person. + +Please don't be offended if someone steps in when you run out of time and moves the reports along to the next person - the idea +is for everyone to be succinct so it's clear what's being worked on and who is blocked. + +We finish with talking points, which are open-ended discussions on any topic related to cert-manager or its sub-projects. +We'll ensure that anyone outside of the core maintainer team who has a talking point goes first. + +### Community Meetings + +Our bi-weekly (i.e. every two weeks) community meetings happen [on Google Meet](https://meet.google.com/iga-jwvx-nye) at [17:00 London time](http://www.thetimezoneconverter.com/?t=17:00&tz=Europe/London) (for dates, check calendar invites or [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U)). + +These meetings are an hour-long chat about cert-manager topics. It's a great way to get involved with contributing for the +first time; to get answers to any questions you might have; or to propose a new feature which needs some explanation. + +If you want to discuss something, please add it to the [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U) +before the meeting. The meeting chair will try to get to everything that was on the notes before the meeting started. + +We try to record these meetings and put them on YouTube so they can be checked later - if you don't want to appear on video please keep +your camera off! + +## Slack + +We have two cert-manager channels on [Kubernetes Slack](https://slack.k8s.io) which we use to chat: + +* [`cert-manager`](https://kubernetes.slack.com/messages/cert-manager): for all users of cert-manager; use this one for any usage related questions +* [`cert-manager-dev`](https://kubernetes.slack.com/messages/cert-manager-dev): for collaboration between cert-manager contributors and maintainers; please only use this for code related questions diff --git a/content/v1.12-docs/contributing/building.md b/content/v1.12-docs/contributing/building.md new file mode 100644 index 0000000000..92fd7a996f --- /dev/null +++ b/content/v1.12-docs/contributing/building.md @@ -0,0 +1,267 @@ +--- +title: Building cert-manager +description: 'cert-manager contributing guide: Building cert-manager' +--- + +cert-manager is built and tested using [make](https://www.gnu.org/software/make/manual/make.html), with a focus on using the standard Go tooling +where possible and keeping system dependencies to a minimum. The cert-manager build system can provision most of its dependencies - including Go - +automatically if required. + +cert-manager's build system fully supports developers who use `Linux amd64`, `macOS amd64` and `macOS arm64`. + +It also has limited support for `Linux arm64`, although that platform is largely untested and isn't fully supported. + +Other operating systems and architectures may work but will likely require hacks and workarounds to develop on. + +## Prerequisites + +There are very few other requirements needed for developing cert-manager, and crucially the build system should tell you with a friendly error +message if there's anything missing. If you think an error message which relates to a missing dependency is unhelpful, we consider that a bug and +we'd appreciate if you raised [an issue](https://github.com/cert-manager/cert-manager/issues/new?assignees=&labels=&template=bug.md) to tell us about it! + +You should install the following tools before you start developing cert-manager: + +- [git](https://git-scm.com/) +- [curl](https://curl.se/) +- [GNU make](https://www.gnu.org/software/make/manual/make.html), `v3.82` or newer +- GNU Coreutils (usually already installed on Linux, available via [homebrew](https://formulae.brew.sh/formula/coreutils) for macOS) +- `jq` (available in Linux package managers and in [homebrew](https://formulae.brew.sh/formula/jq)) +- `docker` (or `podman`, see [Container Engines](#container-engines) below) +- `Go` (optional; see [Go Versions](#go-versions) below) + +## Getting Started + +The vast majority of commands which you're likely to need to use are documented via `make help`. That's probably the first place to start if you're +developing cert-manager. We'll also provide an overview on this page of some of the key targets and things to bear in mind. + +### Go Versions + +cert-manager defaults to using whatever version of Go you've installed locally on your system. If you want to use your system Go, that's totally fine. + +Alternatively, make can provision and "vendor" Go specifically for cert-manager, helping to ensure you use the same version that's used in CI and to +make it easier to get started developing. + +To start using a vendored Go, run: `make vendor-go`. + +You only need to run `vendor-go` once and it'll be "sticky", being used for all future make invocations in your local checkout. + +To return to using your system version of go, run: `make unvendor-go`. + +To check which version of Go is _currently_ being used, run: `make which-go`, which prints the version number of Go and the path to the Go binary. + +```console +# Use a vendored version of go +$ make vendor-go +cd _bin/tools/ && ln -f -s ../downloaded/tools/_go-1.XY.Z-linux-amd64/goroot . +cd _bin/tools/ && ln -f -s ../downloaded/tools/_go-1.XY.Z-linux-amd64/goroot/bin/go . + +# A path to go inside the cert-manager directory indicates that a vendored Go is being used +$ make which-go +go version go1.XY.Z linux/amd64 +go binary used for above version information: /home/user/workspace/cert-manager/_bin/tools/go + +# Go back to the system Go +$ make unvendor-go +rm -rf _bin/tools/go _bin/tools/goroot + +# The binary is now "go" which should be found in $PATH +$ make which-go +go version go1.AB.C linux/amd64 +go binary used for above version information: go +``` + +### Go Workspaces + +In short: Some development tools will complain about cert-manager's module layout; to help with this, generate a +`go.work` file using `make go-workspace`. + +The cert-manager repository as of cert-manager 1.12 contains multiple Go modules, in a setup where only the core module `github.com/cert-manager/cert-manager` +is expected to be imported by third party modules. There are separate modules (which we call submodules), all of which have replace statements for the core +cert-manager module. + +This setup is intentional to convey that these submodules are not intended to be imported by third parties, and to ensure that each submodule always uses +whatever the cert-manager core module version is at the same commit - but this structure can have the side effect that certain development tools and scripts +will not work as expected. + +As an example, `go test ./...` will by default only affect the core module. To test, say, the controller, you'd need to use `cd cmd/controller && go test ./...`. + +This can be avoided through the use of go workspaces, which will handle local replacements for you and work better with editors such as VS Code. + +You can run `make go-workspace` to generate a `go.work` file which should enable `go test ./...` to work across the +whole repo, and which should help editors to understand the module structure. + +Note that go workspaces are not used when testing pull requests in CI. If you see errors in CI which you can't replicate +locally, try building with the `GOWORK` environment variable set to `off` or deleting the `go.work` file. + +### Parallelism + +The cert-manager Makefile is designed to be highly parallel wherever possible. Any build and test commands should be able to be executed in parallel using +standard Make functionality. + +One important caveat is that that Go will default to detecting the number of cores available on the system and spinning up as many threads as it can. If you're +using Make functionality to run multiple builds in parallel, this number of threads can be excessive and actually lead to slower builds. + +It's possible to limit the number of threads Go uses we'd generally recommend doing so when using Make parallelism. + +The best values to use will depend on your system, but we've had success using around half of the available number of cores for Make and limiting Go to between +2 and 4 threads per core. + +For example, using an 8-core machine: + +```bash +# Run 4 make targets in parallel, and limit each `go build` to 2 threads. +make GOMAXPROCS=2 -j4 release-artifacts +``` + +## Testing + +cert-manager's build pipeline and CI infrastructure uses the same Makefile that you use when developing locally, +so there should be no divergence between what the tests run and what you run. That means you should be able to be pretty confident that any changes you make +won't break when tested in CI. + +### Running Local Changes in a Cluster + +It's common that you might want to run a local Kubernetes cluster with your locally-changed copy of cert-manager in it, for manual testing. + +There are make targets to help with this; see [Developing with Kind](./kind.md) for more information. + +### Unit and Integration Tests + +First of all: If you want to test using `go test`, feel free! For unit tests (which we define as any test outside of the `test/` directory), `go test` will +work on a fresh checkout. + +Note that the cert-manager repo is split into multiple modules and unless you're using go workspaces `go test ./...` won't actually run all tests. See [Go Workspaces](./building.md#go-workspaces) above for more details. + +Integration tests may require some external tools to be set up first, so to run the integration tests inside `test/` you might need to run: + +```bash +make setup-integration-tests +``` + +Helper targets are also available which use [`gotestsum`](https://github.com/gotestyourself/gotestsum) for prettier output. It's also possible to +configure these targets to run specific tests: + +```bash +# Run all unit and integration tests +make test + +# Run only unit tests +make unit-test + +# Run only integration tests +make integration-test + +# Run all tests in pkg +make WHAT=./pkg/... test + +# Run unit and integration tests exactly as run in CI +# (NB: usually not needed - this is mostly for JUnit test output for dashboards) +make test-ci +``` + +### End-to-End Testing + +cert-manager's end-to-end tests are a little more involved and have [dedicated documentation](./e2e.md) describing their use. + +### Other Checks + +We run a variety of other tools on every Pull Request to check things like formatting, import ordering and licensing. These checks can all be run locally: + +```bash +make ci-presubmit +``` + +NB: One of these checks currently requires Python 3 to be installed, which is a unique requirement in the code base. We'd like to remove that requirement in the future. + +## Updating CRDs and Code Generation + +Changes to cert-manager's CRDs require some code generation to be done, which will be checked on every pull request. + +If you make changes to cert-manager CRDs, you'll need to run some commands locally before raising your PR. + +This is documented in our [CRDs](./crds.md) section. + +## Building + +cert-manager produces many artifacts for a lot of different OS / architecture combinations, including: + +- Container images +- Client binaries (`cmctl` and `kubectl_cert-manager`) +- Manifests (Helm charts, static YAML) + +All of these artifacts can be built locally using make. + +### Containers + +cert-manager's most important artifacts are the containers which actually run cert-manager in a cluster. We default to using `docker` for this, +but aim to support docker-compatible CLI tools such as `podman`, too. See [Container Engines](#container-engines) for more info. + +There are several targets for building different cert-manager containers locally. These will all default to using `docker`: + +```bash +# Build everything for every architecture +make all-containers + +# Build just the controller containers on every architecture +make cert-manager-controller-linux + +# As above, but for the webhook, cainjector, acmesolver and cmctl containers +make cert-manager-webhook-linux +make cert-manager-cainjector-linux +make cert-manager-acmesolver-linux +make cert-manager-ctl-linux +``` + +#### Container Engines + +NB: This section doesn't apply to end-to-end tests, which might not work outside of Docker at the time of writing. See the [end-to-end documentation](./e2e.md#container-engines) +for more information. + +It's possible to use an alternative container engine to build cert-manager containers. This has been successfully tested using `podman`. + +Configure an alternative container engine by setting the `CTR` variable: + +```bash +# Build everything for every architecture, using podman +make CTR=podman all-containers +``` + +### Client Binaries + +Both `cmctl` and `kubectl_cert-manager` can be built locally for a release. These binaries are built for Linux, macOS and Windows across several architectures. + +```bash +# Build all cmctl binaries for all platforms, then for linux only, then for macOS only, then for Windows only +make cmctl +make cmctl-linux +make cmctl-darwin +make cmctl-windows + +# As above but for kubectl_cert-manager +make kubectl_cert-manager +make kubectl_cert-manager-linux +make kubectl_cert-manager-darwin +make kubectl_cert-manager-windows +``` + +### Manifests + +We use "manifests" as a catch-all term for non-binary artifacts which we build as part of a release including static installation YAML and our Helm chart. + +Everything can be built using make: + +```bash +make helm-chart +make static-manifests +``` + +### Everything + +Sometimes it's useful to build absolutely everything locally, to be sure that a change didn't break some obscure architecture and to build confidence when raising a PR. + +It's not easy to build a _complete_ release locally since a full release includes signatures which depend on KMS keys being configured. Most users probably don't +need that, though, and for this use case there's a make target which will build everything except the signed artifacts: + +```bash +make GOMAXPROCS=2 -j4 release-artifacts +``` diff --git a/content/v1.12-docs/contributing/coding-conventions.md b/content/v1.12-docs/contributing/coding-conventions.md new file mode 100644 index 0000000000..8001cc387d --- /dev/null +++ b/content/v1.12-docs/contributing/coding-conventions.md @@ -0,0 +1,59 @@ +--- +title: Coding Conventions +description: 'cert-manager contributing guide: Coding conventions' +--- + +cert-manager, like most Go projects, delegates almost all stylistic choices to `gofmt`, +with `goimports` on top for organizing imports. Broadly speaking, if you set your editor to run +`goimports` when you save a file, your code will be stylistically correct. + +cert-manager generally also follows the Kubernetes +[coding conventions](https://www.kubernetes.dev/docs/guide/coding-convention/) and the Google +[Go code review comments](https://github.com/golang/go/wiki/CodeReviewComments). + +## Organizing Imports + +Imports should be organized into 3 blocks, with each block separated by two newlines: + +```go +import ( + "stdlib" + + "external" + + "internal" +) +``` + +An example might be the following, taken from +[`pkg/acme/accounts/client.go`](https://github.com/cert-manager/cert-manager/blob/0c71fe7795858b96cabcddabf706d997cd2fba3f/pkg/acme/accounts/client.go): + +```go +import ( + "crypto/rsa" + "crypto/tls" + "net" + "net/http" + "time" + + acmeapi "golang.org/x/crypto/acme" + + acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" + acmeutil "github.com/cert-manager/cert-manager/pkg/acme/util" + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + "github.com/cert-manager/cert-manager/pkg/metrics" + "github.com/cert-manager/cert-manager/pkg/util" +) +``` + +Once this manual split of standard library, external and internal imports has been made, it will be +enforced automatically by `goimports` when executed in the future. + +## UK vs. US spelling + +For the sake of consistency, cert-manager uses en-US spelling for the +documentation in https://cert-manager.io as well as within the cert-manager +codebase. A comprehensive list of en-GB → en-US word substitution is available +on Ubuntu's +[`WordSubstitution`](https://wiki.ubuntu.com/EnglishTranslation/WordSubstitution) +page. \ No newline at end of file diff --git a/content/v1.12-docs/contributing/contributing-flow.md b/content/v1.12-docs/contributing/contributing-flow.md new file mode 100644 index 0000000000..4706a0d169 --- /dev/null +++ b/content/v1.12-docs/contributing/contributing-flow.md @@ -0,0 +1,138 @@ +--- +title: Contributing Flow +description: 'cert-manager contributing guide: Contribution flow' +--- + +All of cert-manager's development is done via +[GitHub](https://github.com/cert-manager/cert-manager) which contains code, issues and pull +requests. + +All code for the documentation and cert-manager.io can be found at [the cert-manager/website repo](https://github.com/cert-manager/website/). +Any issues towards the documentation should also be filed there. + +## GitHub bot + +We use [Prow](https://github.com/k8s-ci-robot/test-infra/tree/master/prow) on all our repositories. +If you've ever looked at a Kubernetes repo, you will probably already have met Prow. Prow will be able to help you in GitHub using its commands. +You can find then all [on the command help page](https://prow.build-infra.jetstack.net/command-help). +Prow will also run all tests and assign certain labels on PRs. + +## Bugs + +All bugs should be tracked as issues inside the +[GitHub](https://github.com/cert-manager/cert-manager/issues) repository. Issues should then be +attached with the `kind/bug` tag. To do this add `/kind bug` to your issue description. +This may then be assigned a priority and milestone to be addressed in a future release. + +The more logs and information you can give about what and how the bug has been +discovered, the faster it can be resolved. + +Critical bug fixes are typically also cherry picked to the current minor stable releases. + +> Note: If you are simply looking for _troubleshooting_ then you should post +> your question to the community `cert-manager` [slack channel](https://slack.k8s.io). +> Many more people read this channel than GitHub issues, it's likely your problem will +> be solved quicker by using Slack. +> Please also check that the bug has not already been filed by searching for key +> terms in the issue search bar. + +### (Re)opening and closing issues + +Prow can assist you to reopen or close issues you file, you can trigger it using `/reopen` or `/close` in a GitHub Issue comment. + +## Features + +Feature requests should be created as +[GitHub](https://github.com/cert-manager/cert-manager/issues) issues. They should contain +clear motivation for the feature you wish to see as well as some possible +solutions for how it can be implemented. +Issues should then be tagged with `kind/feature`. To do this add `/kind feature` to your issue description. + +> Note: It is often a good idea to bring your feature request up on the +> community `cert-manager` [slack channel](https://slack.k8s.io) to discuss whether +> the feature request has already been made or is aligned with the project's +> priorities. + +## Creating Pull Requests + +Changes to the cert-manager code base is done via [pull +requests](https://github.com/cert-manager/cert-manager/pulls). Each pull request +should ideally have a corresponding issue attached that is to be fixed by this +pull request. It is valid for multiple pull requests to resolve a single issue +in the interest of keeping code changes self contained and simpler to review. + +Once created, a team member will assign themselves for review and enable +testing. To make sure the changes get merged, keep an eye out for reviews which +can have multiple cycles. + +If the pull request is a critical bug fix then this will probably +also be cherry picked to the current stable version of cert-manager as a patch +release. + +To let people know that your PR is still a work in progress, we usually add a +`WIP:` prefix to the title of the PR. Prow will then automatically set the label +`do-not-merge/work-in-progress`. + + +### Cherry Picking + +If the pull request contains a critical bug fix then this should be cherry picked in to the current stable cert-manager branch +and [released as a patch release](../installation/supported-releases.md#support-policy). + +To trigger the cherry-pick process, add a comment to the GitHub PR. +For example: +``` +/cherry-pick release-x.y +``` + +The `jetstack-bot` will then create a new branch and a PR against the release branch, +which should be reviewed, approved and merged using the process described above. + +### DCO signoff + +All commits in the PR should be signed off, more info on how to do this is at the [DCO Sign Off](./sign-off.md) page. +Exceptions can only be made for small documentation fixes. + +## Project Management + +Most of cert-manager's project management is done on GitHub, with the help of Prow. + +### When will something be released? + +Our team works using [GitHub milestones](https://github.com/cert-manager/cert-manager/milestones). +When a milestone is set on an Issue it is generally an indication of when we plan to address this. +Prow will apply milestones on merged PRs, this will tell you in which version that PR will land. + +The milestone page will also have an indicated due date when we will release. This might have some delay. +We brief our users/contributors about this in our bi-weekly community meeting, for an up to date status report we recommend joining these. + +### Labels + +We make a heavy use of GitHub labels for PRs and Issues. The ones on PRs are mostly managed by Prow and code reviewers. +In issues we always aim to add 3 types: area, priority and kind. These are set using Prow using `/area`, `/kind` and `/priority`. +Sometimes `/triage` is also added which helps us when following up Issues. + +* Area indicates the code area which is/will need changing +* Kind indicates if it is a `bug` or a `feature` but also can be `documentation` or `cleanup` (general maintenance) +* Priority is the priority it has for the cert-manager team, PRs are still very welcome for those! + +### Assignees meaning in PRs and issues + +Sometimes, you might see someone commenting with the +[`/assign` prow command](https://prow.build-infra.jetstack.net/command-help#assign): + +```plain +/assign @meyskens +``` + +Here is the meaning that we give to the GitHub assignees: + +- On issues, it means that the assignee is working on it. +- On PRs, we use it as a way to know who should be taking a look at the PR at any time: + - When the author is assigned, it means the PR needs work to be done aka "changes requested"; + - When nobody is assigned, it means this PR needs review; + - When someone different from the author is assigned, it means this person is reviewing this PR. + +### Triage Party! + +Every few weeks we will plan a Triage Party meeting, where we use the (Triage Party)[https://triage.build-infra.jetstack.net/] tool to go recent/old issues to prioritise them so we can address them in a timely matter. These meetings are open to everyone and invites will be sent out using our mailing list (warning: despite the word party these meetings are sometimes boring). \ No newline at end of file diff --git a/content/v1.12-docs/contributing/crds.md b/content/v1.12-docs/contributing/crds.md new file mode 100644 index 0000000000..1cce203032 --- /dev/null +++ b/content/v1.12-docs/contributing/crds.md @@ -0,0 +1,65 @@ +--- +title: CRDs +description: 'cert-manager contribution guide: CRDs' +--- + +cert-manager uses [Kubernetes Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) to define +the resources which users interact with when using cert-manager, such as `Certificate`s and `Issuer`s. + +When changes are made to the CRDs in code, there are a couple of extra steps which are required. + +## Generating CRD Updates + +We use [`controller-gen`](https://book.kubebuilder.io/reference/controller-gen.html) to update our CRDs, and [`k8s-code-generator`](https://github.com/kubernetes/code-generator) +for code generation. + +Verifying and updating CRDs and generated code can be done entirely through make. There are two steps; one will update CRDs and one will update generated code: + +```bash +# Check that CRDs and codegen are up to date +make verify-crds verify-codegen + +# Update CRDs based on code +make update-crds + +# Update generated code based on CRD defintions in code +make update-codegen +``` + +## Versions + +cert-manager currently has a single `v1` API version for public use. + +cert-manager API types are defined in [`pkg/apis/certmanager`](https://github.com/cert-manager/cert-manager/tree/master/pkg/apis/certmanager). + +ACME related resources are in [`pkg/apis/acme`](https://github.com/cert-manager/cert-manager/tree/master/pkg/apis/acme). + +### Code Comments + +Code comments on API type fields are converted into documentation on this website as well as appearing in the output of `kubectl explain`. + +That means that `go doc`-style comments on API fields should be written to be user-facing and not developer-facing. For this reason it's also fine to break from +usual Go standards regarding code comments when editing these fields. + +### Internal API Versions + +cert-manager also has an internal API version which lives under [`internal/apis`](https://github.com/cert-manager/cert-manager/tree/master/internal/apis). + +The internal version is only used for validation and conversion and controllers should not generally use it; it's not intended to be user-friendly or stable and can change. +However all new fields also have to be added here for the conversion logic to work. + +For details on conversion and versions, see the [official Kubernetes docs for CRD versioning](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/). + +## Kubebuilder + +While cert-manager doesn't fully use Kubebuilder, CRDs can make use of special Kubebuilder flags such as [validation flags](https://book.kubebuilder.io/reference/markers/crd-validation.html). + +## Making Changes to APIs + +Please see our [API compatibility promise](../installation/api-compatibility.md) for details on which types of changes to APIs are acceptable. + +Generally, the gist is that new fields can be added but that existing fields cannot be removed. + +This also means that when a field is added to a version of the API, it's permanent and its name cannot be changed. Because of this, we try to be cautious when adding new fields. + +The same principles apply to [constants and enumerated types](https://kubernetes.io/docs/reference/using-api/deprecation-policy/#enumerated-or-constant-values). diff --git a/content/v1.12-docs/contributing/dns-providers.md b/content/v1.12-docs/contributing/dns-providers.md new file mode 100644 index 0000000000..400f3d17ee --- /dev/null +++ b/content/v1.12-docs/contributing/dns-providers.md @@ -0,0 +1,23 @@ +--- +title: DNS Providers +description: 'cert-manager contributing guide: Creating DNS providers' +--- + +## Creating DNS Providers + +Due to the large number of requests to support DNS providers to resolve DNS +challenges, it became impractical and infeasible to maintain and test all DNS +providers in the main cert-manager repository. + +For this reason, it was decided that new DNS providers should be supported out-of-tree +by way of external webhooks. + +To implement an external DNS provider webhook, it is recommended to base your +implementation on the [cert-manager webhook-example](https://github.com/cert-manager/webhook-example). + +There's further information available in the configuration section: + +- [ACME DNS01 via webhook](../configuration/acme/dns01/README.md#webhook) +- [Configuring an ACME issuer with external webhook](../configuration/acme/dns01/webhook.md) + +If you're struggling with creating a new DNS webhook, reach out on [Slack](./README.md#slack)! diff --git a/content/v1.12-docs/contributing/e2e.md b/content/v1.12-docs/contributing/e2e.md new file mode 100644 index 0000000000..5037b28fa4 --- /dev/null +++ b/content/v1.12-docs/contributing/e2e.md @@ -0,0 +1,148 @@ +--- +title: Running End-to-End Tests +description: 'cert-manager contribuing guide: End-to-end (E2E) tests' +--- + +cert-manager has an extensive end-to-end (e2e) test suite that verifies functionality against a +real Kubernetes cluster. + +The full end-to-end test suite can take a long time to complete and is run against every pull +request made to the cert-manager project. + +Unless you've made huge changes to the cert-manager codebase --- or to the end-to-end +tests themselves --- you probably don't _need_ to run the tests locally. If you do want to +run the tests, though, this document explains how. + +
+The status of each commit on the master branch is reported on +[`testgrid.k8s.io`](https://testgrid.k8s.io/jetstack-cert-manager-master). Join the +[`cert-manager-dev-alerts`](https://groups.google.com/g/cert-manager-dev-alerts) +Google group to receive email notifications when tests fail. +
+ +## Requirements + +There are no special requirements for the end-to-end tests. All dependencies can be +provisioned automatically through the make build system. + +## Set up End-to-End Tests + +### Create a Cluster + +You can create a kind cluster using Make: + +```console +# Create a cluster using whatever K8s version is default, named "kind" +make e2e-setup-kind + +# Create a cluster using K8s 1.23 named "keith" +make K8S_VERSION=1.23 KIND_CLUSTER_NAME=keith e2e-setup-kind +``` + +**IMPORTANT:** the kind cluster will be set up using a specific service CIDR range to enable certain functionality in end-to-end tests. This CIDR range is not currently configurable. + +Once complete, the cluster is available via `kubectl` as you'd expect. + +### Install Test Dependencies + +There are various dependencies which the end-to-end tests require, all of which can also +be installed via Make: + +```console +make e2e-setup +``` + +If you only need to update or reinstall one of these dependencies in your test cluster, you can instead install named components explicitly to save some time. + +The most common use case for this is to **reinstall cert-manager itself**, say if you've made a change +locally and want to test that change in a cluster: + +```console +# Most important: reinstall cert-manager, including rebuilding changed containers locally +make e2e-setup-certmanager + +# An example of reinstalling something else; reinstall bind +make e2e-setup-bind + +# More generally, see make/e2e-setup.mk for different targets! +``` + +## Run End-to-End Tests + +As with setup, running tests is available through make. In fact, you can just run `make e2e` directly +and avoid having to set anything up manually! + +```console +# Set up a cluster using the defaults if one's not already present, and then run the end-to-end tests +make e2e + +# Set up a K8s 1.23 cluster and then run tests +make K8S_VERSION=1.23 e2e + +# Run tests exactly as they're run in CI; usually not needed +make e2e-ci +``` + +If you don't want to run every test you can focus on specific tests using `GINKGO_FOCUS` syntax, as described in the +[Ginkgo documentation](https://onsi.github.io/ginkgo/#focused-specs): + +```console +make GINKGO_FOCUS=".*my test description" e2e +``` + +## Cluster IP Details + +As mentioned above, the end-to-end tests expect that certain components are deployed in a +specific way and even at specific IP addresses. + +By way of illustration, the following cluster components are deployed with specific IPs: + +| Component / Make Target | Used in | IP | DNS A Record | +| -------------------------- | -------------------------- | ----------- | --------------------------------------- | +| `e2e-setup-bind` | DNS-01 tests | `10.0.0.16` | | +| `e2e-setup-ingressnginx` | HTTP-01 `Ingress` tests | `10.0.0.15` | `*.ingress-nginx.db.http01.example.com` | +| `e2e-setup-projectcontour` | HTTP-01 `GatewayAPI` tests | `10.0.0.14` | `*.gateway.db.http01.example.com` | + +If you don't set these components up correctly, you might see that the ACME HTTP01 (and other) end-to-end tests fail. + +## End-to-End Test Structure + +The end-to-end tests consist of 2 main parts: issuer specific tests and the conformance suite. + +Both parts use [Ginkgo](https://onsi.github.io/ginkgo/#getting-ginkgo) to run their tests under the hood. + +### Conformance Suite + +#### RBAC + +This suite tests all RBAC permissions granted to cert-manager on the cluster to check that it is able to operate correctly. + +#### Certificates + +This suite tests certificate functionality against all issuers. + +#### Feature Sets + +Some issuers don't support certain features, such as for example issuing Ed25519 certificates or adding an email address +to the X.509 SAN extension. + +Each test specifies a used feature using `s.checkFeatures(feature)`, which is then checked against the issuer's +`UnsupportedFeatures` list. Tests which use a feature unsupported by an issuer are skipped for that issuer. + +### Cloud Provider Tests + +The master branch of cert-manager can also be tested against different cloud providers. Currently, tests for [EKS](https://aws.amazon.com/eks/) are present which run as a periodic job once every two days. + +#### Extending The Cloud Provider Tests + +The infrastructure used to run the e2e tests on cloud providers is present in the [cert-manager/test-infra](https://github.com/cert-manager/test-infra) repository. More cloud providers can be added by creating infrastructure for them using [Terraform](https://www.terraform.io/). + +Apart from that, tests for the existing infrastructure can be customized by editing their respective prow jobs present in the [Jetstack testing repository](https://github.com/jetstack/testing/tree/master/config/jobs/cert-manager) repository. Values like the cert-manager version or the cloud provider version are present as variables in Terraform so their values can be changed when using `terraform apply` in the prow jobs, for example, for the [EKS prow job](https://github.com/jetstack/testing/blob/master/config/jobs/cert-manager/cert-manager-periodics.yaml#L524) the cert-manager version being tested can be changed using + +```console +terraform apply -var="cert_manager_version=v1.3.3" -auto-approve +``` + +To see a list of all configurable variables present for a particular infrastructure you can see the `variables.tf` file for that cloud provider's [infrastructure](https://github.com/cert-manager/test-infra). + +> Please note that the cloud provider tests run the e2e tests present in the **master** branch of cert-manager on a predefined version of cert-manager (can be changed in the prow job). Currently, they do **not** test code in a PR, but we have an [issue](https://github.com/cert-manager/cert-manager/issues/4349) tracking that request. diff --git a/content/v1.12-docs/contributing/external-issuers.md b/content/v1.12-docs/contributing/external-issuers.md new file mode 100644 index 0000000000..4d18572fb3 --- /dev/null +++ b/content/v1.12-docs/contributing/external-issuers.md @@ -0,0 +1,77 @@ +--- +title: Implementing External Issuers +description: 'cert-manager contributing guide: External Issuers' +--- + +cert-manager offers a number of [core issuer types](../configuration/README.md) that represent +various certificate authorities. + +Since the number of potential issuers is larger than what could reasonably be supported in the +main cert-manager repository, cert-manager also supports out-of-tree external issuers, and treats +them the same as in-tree issuer types. + +This document is for people looking to _create_ external issuers. For more information on how to +install and configure external issuer types, read the [configuration documentation](../configuration/external.md). + +## General Overview + +An issuer represents a certificate authority that signs incoming certificate +requests. In cert-manager, the `CertificateRequest` resource represents a single +request for a signed certificate, containing the raw certificate request PEM +data as well as other information relating to the desired certificate. + +In cert-manager, each issuer type has its own controller that watches these +`CertificateRequest` resources and checks to see if a given `CertificateRequest` is +configured to use the issuer. + +This is done via the `issuerRef` stanza on the `CertificateRequest` which contains +an issuer `name`, `kind` and `group`. + +`group` denotes an API group such as `cert-manager.io` (which is responsible for all core issuer types). + +`kind` denotes the "kind" resource type of the issuer - usually `Issuer` or `ClusterIssuer`. + +`name` denotes the name of the issuer resource of the specified kind. An example might be `my-ca-issuer`. + +When an issuer controller observes a new `CertificateRequest` which refers to it, +it then ensures that the corresponding issuer resource exists in Kubernetes. + +It then uses the information inside the issuer resource to attempt to create a +signed certificate, based upon the information inside the certificate request. + +## Sample External Issuer + +If you want to create an External Issuer, the best place to start is likely to be the [Sample External Issuer](https://github.com/cert-manager/sample-external-issuer). + +The Sample External Issuer is maintained by the cert-manager team, and its README file has step-by-step instructions +on how to write an external issuer using Kubebuilder and controller-runtime. + +## Approval + +Before signing a certificate, Issuers **must** also ensure that the `CertificateRequest` is +[`Approved`](../concepts/certificaterequest.md#approval). + +If the `CertificateRequest` is not `Approved`, the issuer **must** not process it. Issuers are not +responsible for approving `CertificateRequests` and should refuse to proceed if they find a certificate +that is not approved. + +If a `CertificateRequest` created for an issuance associated with a `Certificate` gets [`Denied`](../concepts/certificaterequest.md#approval), the issuance will be failed by cert-manager's issuing controller. + +## Conditions + +Once a signed certificate has been gathered by the issuer controller, it updates the status of the +`CertificateRequest` resource with the signed certificate. It is then important to update the condition +status of that resource to a ready state, as this is what is used to signal to higher order +controllers - such as the `Certificate` controller - that the resource is ready to be consumed. + +Conversely, if the `CertificateRequest` fails, it is as important to mark the resource as such, as this will +also be used as a signal to higher order controllers. Valid condition states are listed under [concepts](../concepts/certificaterequest.md#conditions). + +## Implementation + +It is recommended that you make use of the [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder) project in order +to implement your external issuer controller. This makes it very simple to generate `CustomResourceDefinitions` and gives +you a lot of controller functionality out of the box. + +If you have further questions on how to implement an external issuer controller, it is best to reach out on [slack](./README.md#slack) +or to join a [community calls](./README.md#meetings). diff --git a/content/v1.12-docs/contributing/featuregates.md b/content/v1.12-docs/contributing/featuregates.md new file mode 100644 index 0000000000..4e6f332967 --- /dev/null +++ b/content/v1.12-docs/contributing/featuregates.md @@ -0,0 +1,47 @@ +--- +Title: Implementing feature gates +description: 'cert-manager contributing guide: Implementing feature gates' +--- + +As of v1 release cert-manager is considered stable. We aim to follow Kubernetes API compatibility policy when making API changes, see [API compatibility](../installation/api-compatibility.md) to avoid breaking users' existing cert-manager installations. +This means that as developers we are somewhat limited in regards to changing existing behavior, i.e renaming or removing API elements or changing their behavior. + +New functionality that is not yet stable[^1] can still be added, but it needs to be placed behind a feature gate. + +## Feature gated API fields + +Feature gated API fields are implemented using `--feature-gates` flags of cert-manager [webhook](../cli/webhook.md) and [controller](../cli/controller.md). + +A feature gated API field is always visible to the user (i.e when running `kubectl explain `), but is only functional if the relevant feature is explicitly enabled via feature flags for both the webhook and controller. + +If a user attempts to apply a resource with the feature gated field set to a non-nil value, but the feature gate is not enabled, the resource will get rejected by the webhook validation. +This mechanism differs from [the one that Kubernetes uses for feature gated API field implementation](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md#new-field-in-existing-api-version) where the field will be simply set to nil if the feature gate is disabled. We chose to use webhook validation instead to make debugging easier for users who are attempting to use the feature gated field, but have forgotten to enable the feature gate. + + +### Implementation + +- Implement the new field and document that it is feature gated and in order to use it the controller and webhook feature gates need enabling +- Add a new [webhook feature gate](https://github.com/cert-manager/cert-manager/blob/3a055cc2f56c1c2874807af4a8f84d0a1c46ccb4/internal/webhook/feature/features.go#L25-L39) for the field +- Update webhook validation checks for the relevant resource kind to ensure that if the feature gated field is set, but the webhook feature gate is not enabled, the resource gets rejected +- Add a new [controller feature gate](https://github.com/cert-manager/cert-manager/blob/2417132b3cd017b5f0974006e03c2b8a540efe3f/internal/controller/feature/features.go#L26-L54) for the field +- Ensure that any control loops that use the feature, check that the feature gate is actually enabled. (This is required to cover edge cases such as if the webhook runs a version of cert-manager where the feature is in GA whereas controller runs an older version where the feature is still in experimental state) +- Ensure that the feature gate is added to cert-manager installation scripts for CI and local tests in [make](https://github.com/cert-manager/cert-manager/blob/134398e939bb2b1401697eaf589405ad469cd609/make/e2e-setup.mk#L165) and [bazel](https://github.com/cert-manager/cert-manager/blob/fd747b42b9ab4b6409b61b7946e8dc14d532e950/devel/addon/certmanager/install.sh#L26) scripts +- Default cert-manger e2e CI tests run with all feature gates for all components enabled. There is an additional optional e2e test that runs with all feature gates disabled. You can trigger that for your PR with `/test pull-cert-manager-e2e-feature-gates-disabled` to verify that all works as expected both with and without the new feature gate. + +### Potential issues + +- The person deploying cert-manager has to remember to set two cert-manager feature gates, one of the webhook one on the controller for the feature to function. Forgetting to set one of them might result in unexpected behavior + +- A user must remember to remove the alpha fields from their manifests when disabling a previously enabled API feature. Failing to do so might result in unexpected behavior- for example forgetting to remove feature gated field from a `Certificate` resource might result in failed renewals at some later point when cert-manager's controller will attempt to update the `Certificate` spec, but the webhook will reject the update due to the feature gated field being set. + +### References + +- cert-manager's [API compatibility promise](../installation/api-compatibility.md) + +- An example implementation of an alpha field is [`AdditionalOutputFormats` field on `Certificate` spec](https://github.com/cert-manager/cert-manager/blob/dbad3d98f3d7d85cadb4bd2c2493faf8b666b313/internal/apis/certmanager/types_certificate.go#L169-L174) + +- [Kubernetes definition of feature stages](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages) + +- Kubernetes [API change design](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md) + +[^1]: For example, functionality that might change in the future in response to user feedback diff --git a/content/v1.12-docs/contributing/google-season-of-docs/2022/README.md b/content/v1.12-docs/contributing/google-season-of-docs/2022/README.md new file mode 100644 index 0000000000..587036e7e1 --- /dev/null +++ b/content/v1.12-docs/contributing/google-season-of-docs/2022/README.md @@ -0,0 +1,10 @@ +--- +title: Google Season of Docs 2022 +description: Google season of docs 2022 proposal +--- + +We registered our interest to participate in Google Season of Docs 2022! + +There's one project proposal: + +[Improve the Navigation and Structure of the cert-manager Website](./improve-navigation-and-structure.md) diff --git a/content/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md b/content/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md new file mode 100644 index 0000000000..8f23636af3 --- /dev/null +++ b/content/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md @@ -0,0 +1,215 @@ +--- +title: Improve the Navigation and Structure of the cert-manager Website +description: Google season of docs 2022 proposal +--- + +## Project Updates + +### 7 Sept 2022: The Webhook Debugging Guide + +friction log for task 3, before +friction log for task 3, after + +At the start of the Google Season of Docs program, we built friction logs for +common user tasks, such as debugging the error "connect: connection refused". +The friction log for this task, visible in the [GSoD work +document](https://docs.google.com/document/d/1O-MFWwtpOcNlrRzsiBvrpGHC10EXnvw0XK37M2nEjzg/edit#bookmark=id.cu9ss8s7yl46), +was to serve as a reference point to see whether the improvements we aimed to +bring would have an impact or not. + +The friction log showed a consistent pattern: the user searches the error on +Google, is confused by GitHub issues that don't have any solutions, then clicks +the second link in the Google results, without much luck. We realized that one +improvement we could make was to add a link to the FAQ page "Troubleshooting +Problems with the Webhook". We found two problems with this FAQ page: + +1. It could not be found by anyone because the error messages were not listed in + the page, meaning that Google would not show the page in the search results. +2. Many error messages were not listed in the page. + +We set ourselves to rewrite this page with the goal of making it error-focused, +meaning that the user would just be able to look for their particular error and +start debugging it. We called it "The Definitive Debugging Guide for the +cert-manager Webhook Pod", and it can be found +[here](../../../troubleshooting/webhook.md). + +### 12 Aug 2022: Improved the layout of the navigation menu + +On displays `>=1280px` the left-hand menu was too narrow to display the nested menu items clearly, +On smaller displays the [responsive CSS](https://tailwindcss.com/docs/responsive-design) actually made the menu larger. +So we've widened it by 1 column on displays `>=1280px` and reduced the width of the content by 1 column to compensate. +This makes the menu much easier to read on laptop and desktop computer screens. + +We fixed an inconsistency in the vertical spacing between menu items with sub-menus and those without. + +And finally, we moved the version selector to the bottom of the side-bar to avoid distracting the reader. + +### 3 August 2022: The cert-manager.io Documentation Survey is now closed + +Thank you to everyone who participated in our documentation survey. +We will use the results to prioritize sections of the website for restructuring and rewriting. +Before the conclusion of this Season-of-Docs we will select a random winner from among the responses and contact you about your prize. + +### 18 July 2022: The cert-manager.io Documentation Survey + +Screenshot 2022-07-18 at 14-35-48 cert-manager documentation survey + +We have created a short survey, to help us identify what are the top-priorities for the cert-manager.io documentation. + +1. We want identify the most useful documentation, so that we don't go and change things that are already working well. +2. We want to know which documentation is not useful, so that we can make improvements. +3. We'd like to hear from new and experienced users about how and how often you use the documentation. +4. And we'd like to know where else you find good information about cert-manager, outside of the cert-manager.io website, +so that we can try and incorporate some of those sources. + +We've added a link to the survey to the banner at the top of this site +and we will also be sharing the link in our Slack channels and mailing lists. + +[Please take 10 minutes to fill in the survey](https://docs.google.com/forms/d/e/1FAIpQLSeqfRkd86_N0L7VOW_ImCT0iyUabhczdiDk2dQDLp55V8kqvw/viewform). + +
+ +### 15 July 2022: New "Getting Started" pages + + + + +We have been auditing the existing documentation to identify some key tasks that our users and potential new users need to carry out. +We have created "friction logs" for some of these tasks. +What this means is that we imagine ourselves in the place of the user and ask, for example, + +> How can I get a Let's Encrypt certificate for my server in Kubernetes? + +So we searched Google and DuckDuckGo for "Let's Encrypt Kubernetes" and to our surprise, cert-manager.io does not feature among the top search results. + +Among the results are some excellent third-party tutorials and videos about using cert-manager to create Let's Encrypt certificates, +and we are grateful to the authors for taking the time to write such detailed content. +But inevitably, some of these refer to much older versions of cert-manager and Kubernetes. +So we have decided to write some official guides, for the cert-manager.io website which demonstrate how to quickly install cert-manager and configure it for Let's Encrypt. +We hope that in time these will be indexed by the search engines and that they will reach the top of the search results for "Let's Encrypt Kubernetes". +The advantages will be that users and potential users will find up-to-date information, +and the cert-manager.io maintainers will receive fewer support requests from new users who are attempting this task. + +Go and read the new [Getting Started Guide for GKE Users](../../../getting-started) and tell us what you think. + +
+ +### 5 May 2022: Announcing Mehak Saeed as Technical Writer + +We are delighted to announce that [Mehak Saeed](https://www.linkedin.com/in/mehak-saeed-29121a12a) will be the technical writer working on this project. +We were extremely impressed with Mehak's presentation during her interview and impressed with her detailed preparations and planning. +We look forward to working with her. + +Thank you to all the other technical writers who applied for this project. + +### 14 April 2022: Project Accepted + +This project was [accepted on 14 April 2022](https://developers.google.com/season-of-docs/docs/participants). + +### 24 March 2022: Project Registered + +We have [registered our interest to participate in Google Season of Docs 2022](https://github.com/google/season-of-docs/pull/483), +and have submitted a single project proposal detailed in the remaining of this +page. + +You have until 27 April 2022 18:00 UTC to apply for the technical writer role. + +We will be sharing the name of the selected candidate on Wed 4 May 2022 at +15:00 London Time (14:00 UTC) on Slack in the channel `#cert-manager-dev`. + +To apply as a technical writer, please let us know by one of the two ways +below: + +- e-mail us at `cert-manager-maintainers@googlegroups.com` with the prefix + `GSoD2022:` in the e-mail subject. +- or open an issue on + [cert-manager/website](https://github.com/cert-manager/website) with the + prefix `GSoD2022:` in the issue title. + +You can join our open standup (every day at 10:30 UK time), and join the +Kubernetes Slack channel `#cert-manager-dev` to know more about this project +proposal. + +## About cert-manager + +cert-manager (current version 1.8.0, first release in October 2017) is an Apache-2.0 licensed Kubernetes add-on to automate the management and issuance of TLS certificates. + +Our typical contributors are Go developers from around the world with experience of the Kubernetes ecosystem with experience contributing to core Kubernetes components and Kubernetes operators. + +Our users are often developers and system administrators who are trying to automate the rotation of TLS certificates for applications running in their Kubernetes clusters. + +Our largest users have cert-manager installed on multiple Kubernetes clusters and managing many thousands of TLS certificates. + +## Project Overview + +Right now the content is not designed with our target audiences in mind. +For example a new user will not easily find a guide explaining how to install cert-manager on AWS and configure it for Let’s Encrypt. +Nor will a Cluster Administrator easily find information about how to optimize cert-manager for a large cluster with many Certificates. +The information exists but is spread across multiple pages and is often not at the obvious page. + +As a visual example, a user looking for a guide on how the Certificate resource can be used may feel helpless when realizing that the "Certificate" page exists twice: once under the "Usage" section, and once under the "Concepts" section. + +![Screenshot of the cert-manager.io website with Usage and Concepts visible in the menu](/images/google-season-of-docs-2022-improve-navigation-and-structure.png) + +(NB: This screenshot is from our old site design but the text and layout are broadly the same) + +We would like a technical writer: + +1. to help us identify our target audiences, and +2. to identify the key tasks of each of these audiences, and +3. re-structure the cert-manager.io website with this in mind. + +For example, we have discussed the following audiences and tasks: Beginner, Cluster Administrator, User, Integrator, New Contributor +and each of these people will be interested in a different set of tasks. +We would like them to quickly and easily find the information they need. + +By making it easier for each group to find the information they need we aim to reduce the number of support queries. + +## Scope + +The scope of this project is as follows: + +1. Identify and describe three target audiences. +2. Identify three key top tasks for each of these audiences. +3. Audit the existing documentation and create a friction log of the current documentation. +4. Using the friction log as a baseline, re-organize the documentation to minimize friction for three top tasks. +6. Incorporate feedback from documentation testers (volunteers in the project) and the wider cert-manager community. +7. Work with the cert-manager team to publish the documentation on cert-manager.io. +8. Create documentation for website contributors explaining how we structure our content around audiences and tasks. + +## Measuring success + +After the technical writer has helped us identify the 3 key tasks for each audience +we will measure a baseline number of clicks required to achieve the task and we will aim to minimize the number of clicks for each task. + +## Timeline + +| Dates | Action Items | +|-------------|--------------------------------------------------| +| May | Orientation | +| May / June | Identify audiences and tasks | +| May / June | Audit and friction log | +| June | Restructuring tasks | +| June / July | Incorporating feedback | +| June / July | Publish to cert-manager.io | +| July | Finish writing guidance for website contributors | +| July | Project Completion | + +## Budget + +| Budget item | Amount ($) | Running Total ($) | Notes | +|-------------------------------------------------------------------------------|-------------|-------------------|---------------------------------| +| Technical writer audit and restructuring of the cert-manager.io documentation | 12,000 | 12,000 | | +| Volunteer stipends | 1,500 | 13,500 | 3 volunteer stipends x 500 each | +| TOTAL | | 13,500 | | + +Regarding the amount of $12,000, we assume that it will be enough to fund one experienced technical writer +part-time (for example, they could work half day from Tuesday to Friday, for a total of 24 days, for 3 months +at a daily rate of $500). + +We will give the "volunteer stipend" to contributors who can show they have one PR within the project +time frame (from 1st May to 30th July) in which a re-write of one page or a set of pages. Before +starting the rewriting, the volunteer will suggest which page they wish to work on either on Slack +(Kubernetes Slack, channel #cert-manager-dev), or in an issue on GitHub, and make sure by asking the +team whether it makes sense to rework this page. As long as at least one positive reaction, the +volunteer can start working. For the stipend to be validated, the PR needs to be reviewed and merged. diff --git a/content/v1.12-docs/contributing/google-season-of-docs/README.md b/content/v1.12-docs/contributing/google-season-of-docs/README.md new file mode 100644 index 0000000000..cb43f69aa8 --- /dev/null +++ b/content/v1.12-docs/contributing/google-season-of-docs/README.md @@ -0,0 +1,8 @@ +--- +title: Google Season of Docs +description: cert-manager and Google Season of Docs +--- + +The cert-manager project participated in Google Season of Docs 2022 + +If you're interested in what happened, you can check out our [2022 proposals!](./2022/README.md). diff --git a/content/v1.12-docs/contributing/importing.md b/content/v1.12-docs/contributing/importing.md new file mode 100644 index 0000000000..2c39a247e3 --- /dev/null +++ b/content/v1.12-docs/contributing/importing.md @@ -0,0 +1,42 @@ +--- +title: Importing cert-manager in Go +description: 'cert-manager contributing guide: Importing cert-manager' +--- + +cert-manager is written in Go, and uses Go modules. You _can_ import it as a Go module, and in some cases +that's fine or even encouraged, but as a rule we generally recommend against importing cert-manager. + +Generally speaking, except for the cases listed below under [When You Might Import cert-manager](#when-you-might-import-cert-manager), +code in the cert-manager repository is *not* covered under any Go module compatibility guarantee. We can and will make breaking +changes, even in publicly exported Go code and even in a minor or patch release of cert-manager. We have made breaking changes like +this in the past. + +Note that this doesn't affect _running_ cert-manager. Our commitment on compatibility is to not break the runtime +functionality of cert-manager, and we take that seriously. + +If you're certain that you *do* need to import cert-manager as a module, see [Module Import Paths](#module-import-paths) +below for a note on how to do that. + +## When You Might Import cert-manager + +You might need to import cert-manager if you're writing Go code which: + +- uses cert-manager custom resources, so you want to import something under `pkg/apis` +- implements an external DNS solver webhook, as in the [webhook-example](https://github.com/cert-manager/webhook-example) +- implements an external issuer, as in the [sample-external-issuer](https://github.com/cert-manager/sample-external-issuer) + +If you think you really need to import other parts of the code, please do reach out and [talk to us](./README.md#slack) so we're +aware of this need! We'll always try to avoid breakage where we can. + +## Module Import Paths + +For all supported versions of cert-manager, the module import path is `github.com/cert-manager/cert-manager`. + +Historically, the cert-manager repository was created on GitHub as `https://github.com/jetstack/cert-manager`, and was later +migrated to `https://github.com/cert-manager/cert-manager`. + +This means that the Go module import path you need may be different if you're trying to use an older version of cert-manager. + +For cert-manager 1.8 and later, use the new path listed above. + +For cert-manager versions older than 1.8 use the old path: `github.com/jetstack/cert-manager` diff --git a/content/v1.12-docs/contributing/kind.md b/content/v1.12-docs/contributing/kind.md new file mode 100644 index 0000000000..a6fc15c14a --- /dev/null +++ b/content/v1.12-docs/contributing/kind.md @@ -0,0 +1,31 @@ +--- +title: Developing with Kind +description: 'cert-manager contributing guide: Using Kind' +--- + +[Kind](https://kind.sigs.k8s.io/) allows you to provision Kubernetes clusters locally using nested Docker containers, +with no requirement for virtual machines. + +These clusters are quick to create and destroy, and are useful for simple testing for +development. cert-manager also uses kind clusters in its [end-to-end tests](./e2e.md). + +## Using Kind Locally + +You should be able to make use of cert-manager's end-to-end test setup logic to create a local Kind cluster for +development. As such, if you want a local cluster you might want to follow some of the details in the +[end-to-end test documentation](./e2e.md). + +If, though, you just want to get a cluster up and running with your local changes to cert-manager running inside +`kind`, try the following: + +```console +make e2e-setup-kind e2e-setup-certmanager +``` + +Or, if you need a specific version of Kubernetes: + +```console +make K8S_VERSION=1.xx e2e-setup-kind e2e-setup-certmanager +``` + +That should leave you with a working cluster which you can interact with using `kubectl`! diff --git a/content/v1.12-docs/contributing/policy.md b/content/v1.12-docs/contributing/policy.md new file mode 100644 index 0000000000..d29f34e41f --- /dev/null +++ b/content/v1.12-docs/contributing/policy.md @@ -0,0 +1,159 @@ +--- +title: Feature Policy +description: 'cert-manager contributing guide: Contribution Policy' +--- + +We love to receive both feature requests and PRs which add to and improve cert-manager; the community is at the heart of what we do! + +If you're thinking of adding a feature, we recommend you read this doc to maximize the chances of your contribution getting the attention it deserves and hopefully to get it merged quickly! + +We recommend creating an issue first for it to be discussed with the cert-manager maintainers. Another possibility is bringing it up in a community meeting for an open discussion on the implementation. + +## Feature Sizing: Getting Your Change Accepted + +We evaluate new features and PRs based on their size and their significance; either they're small or large. + +### Smaller Features + +Many contributions are small. That usually - but not always - means that implementing them won't require many lines of code to be added or changed, and in any case they should be easy +for maintainers to review. A PR being small is a good thing; if you can down-scope your feature to make it smaller, we won't complain! + +If you believe your feature is small, please feel free to just raise a PR and optionally also post a link to your PR in the [cert-manager-dev slack channel](./README.md#slack). Usually a sufficiently small PR can be merged without too much ceremony. If we think it's actually a larger piece of work, we'll let you know. + +### Larger Features + +If you're not sure whether your PR is small, or if you know it's bigger, you'll want to speak to us first before raising a PR. This +will help to ensure that your PR is something we're likely to merge to avoid wasting your time. It'll also make it easier +for us to do the design process. + +#### Design Documents + +Larger feature development should normally start with a design discussion. To get that started, you would raise a PR with a design document against [cert-manager/cert-manager/design](https://github.com/cert-manager/cert-manager/tree/master/design). This allows us to discuss the proposed functionality before starting the work to implement it and serves as a way to document the decisions and reasoning behind them. Ideally, a good design document should allow for faster and more consistent feature development and implementation process by providing a single place where all potential concerns and questions are answered. + +We have a [design template](https://github.com/cert-manager/cert-manager/blob/master/design/template.md) that outlines the structure of the document. +(This is a simplified version of [Kubernetes enhancements KEP template](https://github.com/kubernetes/enhancements/tree/master/keps/NNNN-kep-template)). +Do reach out if you need help with the design. + +Part of the process of discussing a design document may also include a video call with you included! That helps us to plan how a feature should +be implemented and approached. It'll be pretty informal and casual; we just want to make sure we're all on the same page. This call might be part +of a biweekly meeting. + +#### Making Progress with Larger Features + +Larger features with a design document are much more likely to be accepted, and in turn we're much more likely to commit a single +named cert-manager maintainer to the effort to help the PR to be successful. That maintainer might not be able to answer all your +questions, but they should certainly be able to point you in the right direction. + +To get in touch to discuss a feature, please reach out on the [cert-manager-dev slack channel](./README.md#slack), or join a [cert-manager public meeting](./README.md#meetings) to talk about your proposal. + +If you have an open PR with a design document (or have some questions about how to proceed with a design), you should absolutely feel free to add the PR with your design or a link to the relevant GitHub issue to the [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U/edit) for our next biweekly meeting +and join in so we're sure to discuss it and so you can contribute to the discussion! + +#### Large Feature Lifecycle + +1. Informally ask about the feature in slack or a public meeting +2. Create a PR with a lightweight design document using the [design template](https://github.com/cert-manager/cert-manager/tree/master/design/template.md), for discussion +3. Design doc PR gets reviewed - possibly includes meeting or discussion in a biweekly meeting +4. Implement your feature, helped and reviewed by a named cert-manager maintainer + +## Feature Requests We'll Likely Reject + +In some cases, people will request features which we've previously rejected or which for some reason we have to reject. + +It's nothing personal; sometimes we have to make tough choices and especially when it comes to security and maintainability we have to reject certain +proposals. If your feature request is listed below, there's a high chance we'll have to reject it. + +That said, if you think we've made a mistake and that we should reconsider, we're open to chatting - consider joining our [biweekly meetings](./README.md#meetings) to discuss it with us! + +### Vendoring Kubernetes related APIs outside of the `k8s.io/` namespace + +Vendoring project APIs that also vendor `k8s.io/apimachinery`, such as OpenShift, Contour, or Velero, is not recommend because the Kubernetes dependency is likely to conflict with cert-manager's instance. +It could also cause a conflict with different Kubernetes client versions being used. + +If this is needed it is suggested to use a "dynamic client" that converts the objects into internal structures copied into the cert-manager codebase. + +### Additional configuration options for the Helm chart + +cert-manager's Helm chart is intended to allow to create a standard, best practices cert-manager installation with basic configuration options, such as being able to provide flags to cert-manager components, label resources etc. +We do not aim to include every possible configuration option for resources that the chart creates to avoid maintenance burden and because we do not have automated testing for all chart configuration options. Therefore we are likely to not accept PRs that add advanced or niche configuration options to Helm charts- we recommend that users who require that configuration use another mechanism such as [Helm's post-install hooks](https://helm.sh/docs/topics/charts_hooks/). + +### Helm + CRDs + +Helm suggests that CRDs be included in a `crds/` subdirectory of a chart, with the `crd-install` annotation included. This has the unfortunate side effect that CRDs are not upgraded if changed in a later release. + +CRDs being upgraded without being removed and re-installed is essential for cert-manager to move forward. + +This was previously discussed [in the Helm community](https://github.com/helm/helm/issues/5871). + +cert-manager works around this limitation by shipping CRDs in the templates. + +### Helm Subchart capabilities + +cert-manager now has the capability to be [installed as a subchart](../installation/helm.md#installing-cert-manager-as-subchart). + +But you need to be careful when adding it to your umbrella chart. + +This is because the cert-manager installation creates cluster scoped resources like admission webhooks and custom resource definitions. cert-manager should be seen as part of your cluster and should be treated as such for being installed. An apt comparison +to other Kubernetes components would be a LoadBalancer controller or a PV provisioner. + +It is your responsibility to ensure that cert-manager is only installed once in your cluster. +This can be managed via the `condition` parameter of the dependency in your `Chart.yaml`, which allows users to disable the installation of a subchart. The condition parameter must be added when using cert-manager as a subchart to allow users to disable your dependency. + +```yaml +apiVersion: v2 +name: example_chart +description: A Helm chart with cert-manager as subchart +type: application +version: 0.1.0 +appVersion: "0.1.0" +dependencies: + - name: cert-manager + version: v1.8.0 + repository: https://charts.jetstack.io + alias: cert-manager + condition: cert-manager.enabled +``` + +### Secret injection or copying + +cert-manager deals with very sensitive information (all TLS certificates for your services) and has cluster-level access to secret resources. As such, when designing features we need to consider all of the ways these secrets might be abused to escalate privilege. + +Secret data is meant to be securely stored in `Secret` resources and have narrow scoped access privileges for unauthorized users. Because of this, we won't usually add any functionality that allows this data to be copied/injected into any resource +other than a Kubernetes `Secret`. + +#### cainjector + +The cainjector component is a special exception to this rule as it deals in non-sensitive information (CAs, not cert/key pairs). This component is able to inject the `ca.crt` file into predefined fields on `ValidatingWebhookConfiguration`, `MutatingWebhookConfiguration`, and `CustomResourceDefinition` resources from Certificate resources. + +These 3 components are already scoped only for privileged users, and will already give you cluster scoped access to resources. + +If you’re designing a resource that needs a CA Certificate or TLS key pair it is strongly recommended to use a reference to a secret instead of embedding it in a resource. + +### Cross namespace resources + +Namespace boundaries in Kubernetes provide a barrier for access scopes. Apps or users can be limited to only access resources in a certain namespace. + +cert-manager is a controller that operates on cluster wide resources however, and while it may seem interesting to allow access to copy or write certificate data from one namespace to the other, this can cause a bypass of the +namespace security model for all users, which is usually not intended and can be a major a security issue. + +We don't support this behavior; if you believe you need it, and it's intended for your use case then there are other Kubernetes controllers that can do this, although we'd suggest extreme caution. + +### Sign certificates using the Kubernetes CA + +Kubernetes has a Certificate Signing Requests API, and a `kubectl certificates` command which allows you to approve certificate signing requests and have them signed by the certificate authority (CA) of the Kubernetes cluster. This +CA is generally used for your nodes. + +This API and CLI have occasionally been misused to sign certificates for use by pods outside of the control plane; we believe this is a mistake. + +For the security of the Kubernetes cluster it's important to limit access to the Kubernetes certificate authority; such certificates increase the attack surface for the Kubernetes API server since this CA signs certificates for +authorization against the API server. If cert-manager used this cert, it could allow any user with permission to create cert-manager resources to elevate privileges by signing certificates which are trusted for API access. + +[See our FAQ](../faq/README.md#kubernetes-has-a-builtin-certificatesigningrequest-api-why-not-use-that) for more details on this. + +### Integrations with third party infrastructure providers + +We try to not include in core cert-manager new functionality that involves calling third party APIs that we don't have infrastructure to test (or that the maintainers don't have the skills to work with). + +Instead we try to build interfaces such as [external DNS webhook solver](../configuration/acme/dns01/webhook.md) that can be implemented to use cert-manager with a particular third party implementation. +We believe that this is a more sustainable approach as that way folks who have knowledge and skills to work with particular infrastructure can own a project that interacts with it and it lets us avoid merging potentially untested code to core cert-manager. +An example of a PR that might be rejected would be adding a new external DNS solver kind, see https://github.com/cert-manager/cert-manager/pull/1088 diff --git a/content/v1.12-docs/contributing/release-process.md b/content/v1.12-docs/contributing/release-process.md new file mode 100644 index 0000000000..4e0b074fcf --- /dev/null +++ b/content/v1.12-docs/contributing/release-process.md @@ -0,0 +1,599 @@ +--- +title: Release Process +description: 'cert-manager contributing: Release process' +--- + +This document aims to outline the process that should be followed for +cutting a new release of cert-manager. If you would like to know more about +current releases and the timeline for future releases, take a look at the +[Supported Releases](../installation/supported-releases.md) page. + +## Prerequisites + +⛔️ Do not proceed with the release process if you do not meet all of the +following conditions: + +1. The relevant [testgrid dashboard](https://testgrid.k8s.io/cert-manager) should not be failing for the release you're trying to perform. +2. The release process **takes about 40 minutes**. You must have time to complete all the steps. +3. You currently need to be at Jetstack to get the required GitHub and GCP + permissions. (we'd like contributors outside Jetstack to be able to get + access; if that's of interest to you, please let us know). +4. You need to have the GitHub `admin` permission on the cert-manager project. + To check that you have the `admin` role, run: + + ```bash + brew install gh + gh auth login + gh api /repos/cert-manager/cert-manager/collaborators/$(gh api /user | jq -r .login)/permission | jq .permission + ``` + + If your permission is `admin`, then you are good to go. To request the + `admin` permission on the cert-manager project, [open a + PR](https://github.com/jetstack/platform-board/pulls/new) with a link to + here. + +5. You need to be added as an "Editor" to the GCP project + [cert-manager-release](https://console.cloud.google.com/?project=cert-manager-release). + To check if you do have access, try opening [the Cloud Build + page](https://console.cloud.google.com/cloud-build?project=cert-manager-release). + To get the "Editor" permission on the GCP project, open a PR with your name + added to the maintainers list in + [`cert_manager_release.tf`](https://github.com/jetstack/terraform-jetstack/blob/master/cert_manager_release.tf) + + ```diff + --- a/cert_manager_release.tf + +++ b/cert_manager_release.tf + @@ -17,6 +17,7 @@ locals { + var.personal_email["..."], + var.personal_email["..."], + var.personal_email["..."], + + var.personal_email["mael-valais"], + ]) + } + ``` + + You may use the following PR description: + + ```markdown + Title: Access to the cert-manager-release GCP project + + Hi. As stated in "Prerequisites" on the [release-process][1] page, + I need access to the [cert-manager-release][2] project on GCP in + order to perform the release process. Thanks! + + [1]: https://cert-manager.io/docs/contributing/release-process/#prerequisites + [2]: https://console.cloud.google.com/?project=cert-manager-release + ``` + +This guide applies for versions of cert-manager released using `make`, which should be every version from cert-manager 1.8 and later. + +**If you need to release a version of cert-manager 1.7 or earlier** see [older releases](#older-releases). + +First, ensure that you have all the tools required to perform a cert-manager release: + +1. Install the [`release-notes`](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md) CLI: + + ```bash + go install k8s.io/release/cmd/release-notes@v0.13.0 + ``` + +2. Install our [`cmrel`](https://github.com/cert-manager/release) CLI: + + ```bash + go install github.com/cert-manager/release/cmd/cmrel@latest + ``` + +3. Clone the `cert-manager/release` repo: + + ```bash + # Don't clone it from inside the cert-manager repo folder. + git clone https://github.com/cert-manager/release + cd release + ``` + +4. Install the [`gcloud`](https://cloud.google.com/sdk/) CLI. +5. [Login](https://cloud.google.com/sdk/docs/authorizing#running_gcloud_auth_login) + to `gcloud`: + + ```bash + gcloud auth application-default login + ``` + +6. Make sure `gcloud` points to the cert-manager-release project: + + ```bash + gcloud config set project cert-manager-release + export CLOUDSDK_CORE_PROJECT=cert-manager-release # this is used by cmrel + ``` + +7. Get a GitHub access token [here](https://github.com/settings/tokens) + with no scope ticked. It is used only by the `release-notes` CLI to + avoid API rate limiting since it will go through all the PRs one by one. + +## Minor releases + +A minor release is a backwards-compatible 'feature' release. It can contain new +features and bug fixes. + +### Release schedule + +We aim to cut a new minor release once per month. The rough goals for each +release are outlined as part of a GitHub milestone. We cut a release even if +some of these goals are missed, in order to keep up release velocity. + +### Process for releasing a version + +
+🔰 Please click on the **Edit this page** button on the top-right corner of this +page if a step is missing or if it is outdated. +
+ +1. Make sure to note which type of release you are doing. That will be helpful + in the next steps. + + | Type of release | Example of git tag | + |------------------------------------|--------------------| + | initial alpha release | `v1.3.0-alpha.0` | + | subsequent alpha release | `v1.3.0-alpha.1` | + | initial beta release | `v1.3.0-beta.0` | + | subsequent beta release | `v1.3.0-beta.1` | + | final release | `v1.3.0` | + | (optional) patch pre-release[^1] | `v1.3.1-beta.0` | + | patch release (or "point release") | `v1.3.1` | + +[^1]: One or more "patch pre-releases" may be created to allow voluntary community testing of a bug fix or security fix before the fix is made generally available. The suffix `-beta` must be used for patch pre-releases. + +2. **(final release only)** Make sure that a PR with the new upgrade + document is ready to be merged on + [cert-manager/website](https://github.com/cert-manager/website). See for + example, see + [upgrading-1.0-1.1](https://cert-manager.io/docs/installation/upgrading/upgrading-1.0-1.1/). + +3. Check that the `origin` remote is correct. To do that, run the following + command and make sure it returns the upstream + `https://github.com/cert-manager/cert-manager.git`: + + ```bash + # Must be run from the cert-manager repo folder. + git remote -v | grep origin + ``` + + It should show: + + ```text + origin https://github.com/jetstack/cert-manager (fetch) + origin https://github.com/jetstack/cert-manager (push) + ``` + +4. Place yourself on the correct branch: + + - **(initial alpha and subsequent alpha)**: place yourself on the `master` + branch: + + ```bash + git checkout master + git pull origin master + ``` + + - **(initial beta only)** The release branch doesn't exist yet, so let's + create it and push it: + + ```bash + # Must be run from the cert-manager repo folder. + git checkout master + git pull origin master + git checkout -b release-1.12 master + git push origin release-1.12 + ``` + + **GitHub permissions**: `git push` will only work if you have the `admin` + GitHub permission on the cert-manager repo to create or push to the + branch, see [prerequisites](#prerequisites). If you do not have this + permission, you will have to open a PR to merge master into the release + branch), and wait for the PR checks to become green. + + - **(subsequent beta, patch release and final release)**: place yourself on + the release branch: + + ```bash + git checkout release-1.12 + git pull origin release-1.12 + ``` + + You don't need to fast-forward the branch because things have been merged + using `/cherry-pick release-1.0`. + + **Note about the code freeze:** + + The first beta starts a new "code freeze" period that lasts until the + final release. Just before the code freeze, we fast-forward everything + from master into the release branch. + + During the code freeze, we continue merging PRs into master as usual. + + We don't fast-forward master into the release branch for the second (and + subsequent) beta, and only `/cherry-pick release-1.0` the fixes that should be part + of the subsequent beta. + + We don't fast-forward for patch releases and final releases; instead, we + prepare these releases using the `/cherry-pick release-1.0` command. + + > Note about branch protection: The release branches are protected by [GitHub branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule), which is [configured automatically by Prow](https://github.com/jetstack/testing/blob/500b990ad1278982b10d57bf8fbca383040d2fe8/config/config.yaml#L27-L36). + > This prevents anyone *accidentally* pushing changes directly to these branches, even repository administrators. + > If you need, for some reason, to fast forward the release branch, + > you should delete the branch protection for that release branch, using the [GitHub branch protection web UI](https://github.com/cert-manager/cert-manager/settings/branches). + > This is only a temporary change to allow you to update the branch. + > [Prow will re-apply the branch protection within 24 hours](https://docs.prow.k8s.io/docs/components/optional/branchprotector/#updating). + +5. Create the tag for the new release locally and push it upstream (starting the cert-manager build): + + ```bash + RELEASE_VERSION=v1.8.0-beta.0 + git tag -m"$RELEASE_VERSION" $RELEASE_VERSION + # be sure to push the named tag explicitly; you don't want to push any other local tags! + git push origin $RELEASE_VERSION + ``` + + **GitHub permissions**: `git push` will only work if you have the + `admin` GitHub permission on the cert-manager repo to create or push to + the branch, see [prerequisites](#prerequisites). If you do not have this + permission, you will have to open a PR to merge master into the release + branch), and wait for the PR checks to become green. + + For recent versions of cert-manager, the tag being pushed will trigger a Google Cloud Build job to start, + kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to + the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). + +6. Generate and edit the release notes: + + 1. Use the following two tables to understand how to fill in the four + environment variables needed for the next step. These four environment + variables are documented on the + [README](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md#options) + for the Kubernetes `release-notes` tool. + + | Variable | Description | + | ----------------- | --------------------------------------- | + | `RELEASE_VERSION` | The git tag | + | `START_TAG`\* | The git tag of the "previous"\* release | + | `END_REV` | Name of your release branch (inclusive) | + | `BRANCH` | Name of your release branch | + + Examples for each release type (e.g., initial alpha release): + + | Variable | Example 1 | Example 2 | Example 2 | Example 3 | Example 4 | + | ----------------- | ---------------- | ---------------- | ---------------- | ------------- | ------------- | + | | | | | | | + | | initial alpha | subsequent alpha | beta release | final release | patch release | + | `RELEASE_VERSION` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.3.0-beta.0` | `v1.3.0` | `v1.3.1` | + | `START_TAG`\* | `v1.2.0` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.2.0`\*\* | `v1.3.0` | + | `END_REV` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | + | `BRANCH` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | + + > \*The git tag of the "previous" release (`START_TAG`) depends on which + > type of release you count on doing. Look at the above examples to + > understand a bit more what those are. + + > \*\*Do not use a patch here (e.g., no `v1.2.3`). It must be `v1.2.0`: + > you must use the latest tag that belongs to the release branch you are + > releasing on; in the above example, the release branch is + > `release-1.3`, and the latest tag on that branch is `v1.2.0`. + + After finding out the value for each of the 4 environment variables, set + the variables in your shell (for example, following the example 1): + + ```bash + export RELEASE_VERSION="v1.3.0-alpha.0" + export START_TAG="v1.2.0" + export END_REV="release-1.3" + export BRANCH="release-1.3" + ``` + + 2. Generate `release-notes.md` at the root of your cert-manager repo folder + with the following command: + + ```bash + # Must be run from the cert-manager folder. + export GITHUB_TOKEN=*your-token* + git fetch origin $BRANCH + export START_SHA="$(git rev-list --reverse --ancestry-path $(git merge-base $START_TAG $BRANCH)..$BRANCH | head -1)" + release-notes --debug --repo-path cert-manager \ + --org cert-manager --repo cert-manager \ + --required-author "jetstack-bot" \ + --output release-notes.md + ``` + +

+ The GitHub token **does not need any scope**. The token is required + only to avoid rate-limits imposed on anonymous API users. +

+ + 3. Replace the GitHub issue numbers and GitHub handles (e.g., `#1234` or + `@maelvls`) with actual links: + + ```bash + sed release-notes.md \ + -e 's$#([0-9]+)$[#\1](https://github.com/cert-manager/cert-manager/pull/\1)$g' \ + -e 's$@(\w+)$[@\1](https://github.com/\1)$g' \ + -E \ + -i + ``` + + 4. Sanity check the notes: + + - Make sure the notes contain details of all the features and bug + fixes that you expect to be in the release. + - Add additional blurb, notable items and characterize change log. + + You can see the commits that will go into this release by using the + [GitHub compare](https://github.com/cert-manager/cert-manager/compare). For + example, while releasing `v1.0.0`, you want to compare it with the + latest pre-released version `v1.0.0-beta.1`: + + ```text + https://github.com/cert-manager/cert-manager/compare/v1.0.0-beta.1...master + ``` + + 4. **(final release only)** Check the release notes include all changes + since the last final release. + +7. Check that the build is complete and send Slack messages about the release: + + 1. For recent versions of cert-manager, the build will have been automatically + triggered by the tag being pushed earlier. You can check if it's complete on + the [GCB Build History](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). + + 2. If you're releasing an older version of cert-manager (earlier than 1.10) then the automatic build + will failed because the GCB config for that build wasn't backported. + In this case, you'll need to trigger the build manually using `cmrel`, which takes about 5 minutes: + + ```bash + # Must be run from the "cert-manager/release" repo folder. + cmrel makestage --ref=$RELEASE_VERSION + ``` + + This build takes ~5 minutes. It will build all container images and create + all the manifest files, sign Helm charts and upload everything to a storage + bucket on Google Cloud. These artifacts will then be published and released + in the next steps. + + 3. In any case, send a first Slack message to `#cert-manager-dev`: + +

+ Releasing 1.2.0-alpha.2 🧵 +

+ +

+ 🔰 Please have a quick look at the build log as it might contain some unredacted + data that we forgot to hide. We try to make sure the sensitive data is + properly redacted but sometimes we forget to update this. +

+ + 3. Send a second Slack message in reply to this first message with the + Cloud Build job link. For example, the message might look like: + +

+ Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237 +

+ +8. Run `cmrel publish`: + + 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are + valid. Run the following command: + + ```bash + # Must be run from the "cert-manager/release" repo folder. + cmrel publish --release-name "$RELEASE_VERSION" + ``` + + You can view the progress by clicking the Google Cloud Build URL in the + output of this command. + + 2. While the build is running, send a third Slack message in reply to the first message: + +

+ Follow the `cmrel publish` dry-run build: https://console.cloud.google.com/cloud-build/builds16f6f875-0a23-4fff-b24d-3de0af207463?project=1021342095237 +

+ + 3. Now publish the release artifacts for real. The following command will publish the artifacts to GitHub, `Quay.io` and to our + [helm chart repository](https://charts.jetstack.io): + + ```bash + # Must be run from the "cert-manager/release" repo folder. + cmrel publish --nomock --release-name "$RELEASE_VERSION" + ``` + +
+ ⏰ Upon completion there will be: +
    +
  1. + A draft release of cert-manager on GitHub +
  2. +
  3. + A pull request containing the new Helm chart +
  4. +
+
+ + 4. While the build is running, send a fourth Slack message in reply to the first message: + +

+ Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237 +

+ +9. Publish the GitHub release: + + 1. Visit the draft GitHub release and paste in the release notes that you + generated earlier. You will need to manually edit the content to match + the style of earlier releases. In particular, remember to remove + package-related changes. + + 2. **(initial alpha, subsequent alpha and beta only)** Tick the box "This is + a pre-release". + + 3. **(final release and patch release)** Tick the box "Set as the latest + release". + + 4. Click "Publish" to make the GitHub release live. + +10. Merge the pull request containing the Helm chart: + + The Helm charts for cert-manager are served using Cloudflare pages + and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). + The `cmrel publish --nomock` step (above) will have created a PR in this repository which you now have to review and merge, as follows: + + 1. [Visit the pull request](https://github.com/jetstack/jetstack-charts/pulls) + 2. Review the changes + 3. Fix any failing checks + 4. Merge the PR + 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). + +11. **(final release only)** Add the new final release to the + [supported-releases](../installation/supported-releases.md) page. + +12. Open a PR for a [Homebrew](https://brew.sh) formula update for `cmctl`. + + Assuming you have `brew` installed, you can use the `brew bump-formula-pr` + command to do this. You'll need the new tag name and the commit hash of that + tag. See `brew bump-formula-pr --help` for up to date details, but the command + will be of the form: + + ```bash + brew bump-formula-pr --dry-run --tag v0.10.0 --revision da3265115bfd8be5780801cc6105fa857ef71965 cmctl + ``` + + Replacing the tag and revision with the new ones. + + This will take time for the Homebrew team to review. Once the pull reqeust + against https://github.com/homebrew/homebrew-core has been opened, continue + with further release steps. + +13. Post a Slack message as an answer to the first message. Toggle the check + box "Also send to `#cert-manager-dev`" so that the message is well + visible. Also cross-post the message on `#cert-manager`. + +

+ https://github.com/cert-manager/cert-manager/releases/tag/v1.0.0 🎉 +

+ +14. **(final release only)** Show the release to the world: + + 1. Send an email to + [`cert-manager-dev@googlegroups.com`](https://groups.google.com/g/cert-manager-dev) + with the `release` label + ([examples](https://groups.google.com/g/cert-manager-dev?label=release)). + + 2. Send a tweet on the cert-manager Twitter account! Login details are in Jetstack's 1password (for now). + ([Example tweet](https://twitter.com/CertManager/status/1612886311957831680)). Make sure [@JetstackHQ](https://twitter.com/JetstackHQ) retweets it! + + 3. Send a toot from the cert-manager Mastodon account! Login details are in Jetstack's 1password (for now). + ([Example toot](https://infosec.exchange/@CertManager/109666434738850493)) + +15. Proceed to the post-release steps: + + 1. **(initial beta only)** Create a PR on + [cert-manager/release](https://github.com/cert-manager/release) in order to + add the new release to our list of periodic ProwJobs. Use [this PR](https://github.com/jetstack/testing/pull/774/) as an example. + + 2. **(initial beta only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and + open a PR to [cert-manager/testing](https://github.com/jetstack/testing) adding the generated prow configs. + Use [this PR](https://github.com/jetstack/testing/pull/766) as an example. + + 3. If needed, open a PR to + [`cert-manager/website`](https://github.com/cert-manager/website) in + order to: + + - Update the section "How we determine supported Kubernetes versions" on + the [supported-releases](../installation/supported-releases.md) page. + - Add any new release notes, if needed. + + 4. **(final release only)** Create a PR on + [cert-manager/release](https://github.com/cert-manager/release), + removing the now unsupported release version (2 versions back) in this file: + + ```plain + prowspecs/specs.go + ``` + + This will remove the periodic ProwJobs for this version as they're no longer needed. + + 5. **(final release only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and + open a PR to [jetstack/testing](https://github.com/jetstack/testing) adding the generated prow configs. + + 6. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/jetstack/testing) + and update the [milestone_applier](https://github.com/jetstack/testing/blob/3110b68e082c3625bf0d26265be2d29e41da14b2/config/plugins.yaml#L69) + config so that newly raised PRs on master are applied to a new milestone + for the next release. E.g. if master currently points at the `v1.10` milestone, change it to point at `v1.11`. + + If the [milestone](https://github.com/cert-manager/cert-manager/milestones) for the next release doesn't exist, + create it first. If you consider the milestone for the version you just released to be complete, close it. + + 7. **(final release only)** Open a PR to + [`cert-manager/website`](https://github.com/cert-manager/website) in + order to: + + - Update the section "Supported releases" in the + [supported-releases](../installation/supported-releases.md) page. + - Update the section "How we determine supported Kubernetes versions" on + the [supported-releases](../installation/supported-releases.md) page. + In the table, set "n/a" for the line where "next periodic" is since + these tests will be disabled until we do our first alpha. + - Update the [API docs](../reference/api-docs.md) and [CLI docs](../cli/README.md) by running `scripts/gendocs/generate` + and commit any changes to a branch and create a PR to merge those into + `master` or `release-next` depending on whether this is a minor or + patch release. + + 8. Ensure that any installation commands in + [`cert-manager/website`](https://github.com/cert-manager/website) install + the latest version. This should be done after every release, including + patch releases as we want to encourage users to always install the latest + patch. In addition, ensure that release notes for the latest version are added. + + 9. Open a PR against the Krew index such as [this one](https://github.com/kubernetes-sigs/krew-index/pull/1724), + bumping the versions of our kubectl plugins. This is likely only worthwhile if + cmctl / kubectl plugin functionality has changed significantly or after the first release of a new major version. + + 10. Create a new OLM package and publish to OperatorHub + + cert-manager can be [installed](https://cert-manager.io/docs/installation/operator-lifecycle-manager/) using Operator Lifecycle Manager (OLM) + so we need to create OLM packages for each cert-manager version and publish them to both + [`operatorhub.io`](https://operatorhub.io/operator/cert-manager) and the equivalent package index for RedHat OpenShift. + + Follow [the cert-manager OLM release process](https://github.com/cert-manager/cert-manager-olm#release-process) and, once published, + [verify that the cert-manager OLM installation instructions](https://cert-manager.io/docs/installation/operator-lifecycle-manager/) still work. + +## Older Releases + +The above guide only applies for versions of cert-manager from v1.8 and newer. + +Older versions were built using Bazel and this difference in build process is reflected in the release process. + +### cert-manager 1.6 and 1.7 + +Follow [this older version][older-release-process] of the release process on GitHub, rather than the guide on this website. + +The most notable difference is you'll call `cmrel stage` rather than `cmrel makestage`. You should be fine to use the latest +version of `cmrel` to do the release. + +### cert-manager 1.5 and earlier + +If you're releasing version 1.5 or earlier you must also be sure to install a different version of `cmrel`. + +In the step where you install `cmrel`, you'll want to run the following instead: + +```bash +go install github.com/cert-manager/release/cmd/cmrel@cert-manager-pre-1.6 +``` + +This will ensure that the version of `cmrel` you're using is compatible with the version of cert-manager you're releasing. + +In addition, when you check out the `cert-manager/release` repository you should be sure to check out the `cert-manager-pre-1.6` tag in that repo: + +```bash +git checkout cert-manager-pre-1.6 +``` + +Other than the different `cert-manager/release` tag and `cmrel` version, you can follow the [same older release documentation][older-release-process] as +is used for 1.6 and 1.7 - just remember to change the version of `cmrel` you install! + +[older-release-process]: https://github.com/cert-manager/website/blob/6fa0db74de0ae17d7be638a08155d1b4e036aaa9/content/en/docs/contributing/release-process.md?plain=1 diff --git a/content/v1.12-docs/contributing/security.md b/content/v1.12-docs/contributing/security.md new file mode 100644 index 0000000000..6d33165ede --- /dev/null +++ b/content/v1.12-docs/contributing/security.md @@ -0,0 +1,13 @@ +--- +title: Reporting Security Issues +description: 'cert-manager contributing: Security policy' +--- + +Security is the number one priority for cert-manager. If you think you've +found a vulnerability in any cert-manager project, please follow the +[vulnerability reporting process](https://github.com/cert-manager/cert-manager/blob/master/SECURITY.md) +documented in the main cert-manager repository. + +The reporting process is the same for all repositories under the +cert-manager organization. The process is documented in one place to ensure +a single source of truth and a single list of [security contacts](https://github.com/cert-manager/cert-manager/blob/master/SECURITY_CONTACTS.md). \ No newline at end of file diff --git a/content/v1.12-docs/contributing/sign-off.md b/content/v1.12-docs/contributing/sign-off.md new file mode 100644 index 0000000000..2e0dd8cbfa --- /dev/null +++ b/content/v1.12-docs/contributing/sign-off.md @@ -0,0 +1,77 @@ +--- +title: DCO Sign Off +description: 'cert-manager contributing: DCO Sign-off' +--- + +All contributors to the project retain copyright to their work, but must only submit +work which they have the rights to submit. + +We require all contributors to acknowledge that they have the rights to the code they're contributing +by signing their commits in git using a "DCO Sign Off". Note that this is different to "commit signing" +using something like PGP or [`gitsign`](https://github.com/sigstore/gitsign)! + +Any copyright notices in a cert-manager repo should specify the authors as +"The cert-manager Authors". + +To sign your work, pass the `--signoff` option to `git commit` or `git rebase`: + +```bash +# Sign off a commit as you're making it +git commit --signoff -m"my commit" + +# Add a signoff to the last commit you made +git commit --amend --signoff + +# Rebase your branch against master and sign off every commit in your branch +git rebase --signoff master +``` + +This will add a line similar to the following at the end of your commit: + +```text +Signed-off-by: Joe Bloggs +``` + +By signing off a commit you're stating that you certify the following: + +```text +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +That statement is taken from [https://developercertificate.org/](https://developercertificate.org/). diff --git a/content/v1.12-docs/contributing/signing-keys.md b/content/v1.12-docs/contributing/signing-keys.md new file mode 100644 index 0000000000..74d5382d86 --- /dev/null +++ b/content/v1.12-docs/contributing/signing-keys.md @@ -0,0 +1,72 @@ +--- +title: Signing Keys +description: 'cert-manager contributing: Code signing / Signing keys' +--- + +This page describes the bootstrapping process for a key, including how to do it and why a bootstrapping +process is required. + +## What do we Serve? + +To facilitate verification of signatures, we serve public key information from the cert-manager website +directly. It's important to serve the keys from a different location to where the artifacts are hosted; if the +keys were hosted at the same location as the artifacts, an attacker able to change the artifacts would be able +to also change the keys! + +We serve several key types under `static/public-keys`: + +- `cert-manager-pgp-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.asc`: ASCII-armored PGP public key, used for verifying signatures on helm charts via `helm verify` (after being converted to a keyring) +- `cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg`: Old style GPG keyring, needed by the `--keyring` parameter to `helm verify`. See Keyring below. +- `cert-manager-pubkey-2021-09-20.pem`: The raw, PEM-encoded public key used for signing. Cannot be used with GPG (and therefore helm), but should be used for other verification types. + +## Background / Architecture + +Code signing for cert-manager artifacts is done entirely using cloud KMS keys, to ensure that nobody +can get access to the private keys in plain-text; all signing operations using the key are therefore +done through cloud APIs and are logged. + +Currently, all keys are on Google KMS, since the rest of cert-manager's release infrastructure is also +in GCP. The key - and the role bindings which allow access to it - are specified in terraform in a closed +source Jetstack repo. + +## Why Bootstrap? + +While the private key is not retrievable for a KMS key, the public key is and _must_ be retrieved so that +end-users can verify signatures made by the key. In GCP, retrieving the public key is itself an +[API call](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys.cryptoKeyVersions/getPublicKey) +which returns the raw key in a PEM encoded format. + +That PEM-encoded public key works for some cases (e.g. verifying container signature made using `cosign`) but +it's not sufficient for Helm chart verification, since Helm chart signing (sadly) requires the use of PGP. + +## Bootstrapping a PGP Identity + +It's possible to use a shim to use GCP KMS as a PGP key which enables us to avoid having two separate signing keys, +but PGP public identities are slightly more complicated than plain public keys; they also contain a name, +creation time, comment and email address to identify the signer. This public "identity" must itself be signed by the +private key (to prove that the information in the identity is legitimate). + +This bootstrapping can be done using the cert-manager release tool, `cmrel`: + +```console +# note that the key name might not exactly match this in the future +$ cmrel bootstrap-pgp --key "projects/cert-manager-release/locations/europe-west1/keyRings/cert-manager-release/cryptoKeys/cert-manager-release-signing-key/cryptoKeyVersions/1" +``` + +This will trigger a cloud build job which will output both the armored PGP identity and the raw PEM public key; the values +can be copied from the job output. + +### GPG Keyring + +As an additional UX feature, we can also generate a GPG keyring from the PGP identity, since the keyring is what's required +by the Helm CLI to actually validate a chart: + +```console +# Example of verifying a chart. +$ helm verify --keyring cert_manager_keyring_1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg /path/to/chart.tgz +Signed by: cert-manager Maintainers +Using Key With Fingerprint: 1020.... +Chart Hash Verified: sha256:bb86... +``` + +The keyring can be generated using [this script](https://github.com/cert-manager/release/blob/a219e18b2e64ef078bf73b3641d589b43d1fccb8/hack/helm_keyring.sh). \ No newline at end of file diff --git a/content/v1.12-docs/contributing/third-party-code-donation.md b/content/v1.12-docs/contributing/third-party-code-donation.md new file mode 100644 index 0000000000..f53da8e3c6 --- /dev/null +++ b/content/v1.12-docs/contributing/third-party-code-donation.md @@ -0,0 +1,79 @@ +--- +title: Donating Third Party Code to cert-manager +description: 'cert-manager contributing: Third party code donations' +--- + +The cert-manager project welcomes external contributions and has benefited greatly from thousands +of commits from hundreds of different contributors. Most code is usually committed through pull +requests to a specific repo, whether that be the main cert-manager repository or one of the associated +repositories such as the website. + +Some contributions aren't as well suited to that kind of workflow, however. That would most likely +be because their functionality doesn't belong in any particular existing cert-manager repo, while still +relating to the cert-manager project. + +This document aims to address the donation of code to the cert-manager project, and to provide a +framework for sustainable contributions which can be tested and relied upon going forwards by both +cert-manager maintainers and users. + +The requirements in this document are based in part on what's done for CoreDNS, Envoy, Kubernetes +and containerd. + +## Requirements + +1. Code must be licensed appropriately, including any dependencies + We'd prefer [Apache 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) since that's + what cert-manager [uses](https://github.com/cert-manager/cert-manager/blob/master/LICENSE), but the + license must be [OSI approved](https://opensource.org/licenses). +2. Code must conform to CNCF standards and due diligence requirements + You don't need to go over this with a fine-toothed comb; the intent here is that no code donation + should have a negative effect on cert-manager's progress as a CNCF project. See the + [CNCF due diligence template](https://github.com/cncf/toc/blob/main/process/dd-review-template.md) +3. Must be sponsored by an existing maintainer + An existing regular contributor to cert-manager must sponsor the adoption of any third party code + donation. This ensures that there's a single point of contact for the party donating the code. +4. Must pass cert-manager conformance tests + This might not apply to all donations, but where conformance tests exist any donated code must + pass them. E.g. for [external issuers](https://github.com/cert-manager/cert-manager/blob/dffbf391dbb0fc6c1cfea62e561a9c6f54362ab0/test/e2e/suite/conformance/certificates/external/external.go#L41-L62) +5. Must provide a point-of-contact for questions about the project for at least 3 months after acceptance + We don't anticipate that we'd need to reach out often after the donation has been accepted, + but it's important to have someone we can reach out to if we need to. +6. The donation must be a defined extension type or justify why it doesn't belong in the main repositories + E.g. an ACME DNS solver, a custom issuer or an ACME HTTP solver +7. Code must have a similar level of quality to cert-manager itself + This could be enforced by, for example, running static analysis tools on the code base similar to + those used by cert-manager. +8. Code must have a non-trivial test suite, including both unit tests and end-to-end tests + These tests must be able to be run in their entirety after a PR is raised against the repo. We don't + need 100% code coverage, but there should be tests for important functionality. +9. The project must adopt the cert-manager security policy and link back to the policy, as in e.g. + the [istio-csr `SECURITY.md`](https://github.com/cert-manager/istio-csr/blob/master/SECURITY.md) +10. Must have DCO sign-offs or coverage for all commits + To ensure that all code can legally be donated, all commits should have DCO sign-off or else have + a positive affirmation made by each contributor prior to donation. See below. + +## Preferences + +These items are not absolutely necessary but they definitely help if a code donation is to be accepted. + +- Should be written in Go + We don't _need_ code to be written in Go, but we'd much prefer that it is. Since cert-manager itself + is written in Go, code donations in Go allow us to use existing experience and tooling on Go code. + +## DCO Signoff + +As a method of ensuring that the donator has permission to donate the code, we require DCO sign-offs - +or something equivalent - to be in place at the time of the donation. + +The cert-manager [DCO signoff process](https://cert-manager.io/docs/contributing/sign-off/) +would be appropriate. Existing contributors could bootstrap this process by creating an empty signed-off +with a note that previous code should be considered signed off as of that commit: + +```bash +git commit --allow-empty --signoff --message="bootstrapping DCO signoff for past commits" +``` + +## After Donation + +Code files in the donated repository must be updated to include the relevant +[cert-manager boilerplate](https://github.com/cert-manager/cert-manager/blob/master/hack/boilerplate/boilerplate.go.txt) \ No newline at end of file diff --git a/content/v1.12-docs/faq/README.md b/content/v1.12-docs/faq/README.md new file mode 100644 index 0000000000..9bb31f91b5 --- /dev/null +++ b/content/v1.12-docs/faq/README.md @@ -0,0 +1,182 @@ +--- +title: Frequently Asked Questions (FAQ) +description: Find answers to some frequently asked questions about cert-manager +--- + +On this page you will find answers to some frequently asked questions about cert-manager. + +## Terminology + +### What does `publicly trusted` and `self-signed` mean? + +These terms are defined in the [TLS Terminology page](../reference/tls-terminology.md). + +### What do the terms `root`, `intermediate` and `leaf` _certificate_ mean? + +These terms are defined in the [TLS Terminology page](../reference/tls-terminology.md). + +## Certificates + +### Can I trigger a renewal from cert-manager at will? + +This is a feature in cert-manager starting in `v0.16` using the `cmctl` CLI. More information can be found on [the renew command's page](../reference/cmctl.md#renew) + +### When do certs get re-issued? + +To determine if a certificate needs to be re-issued, cert-manager looks at the the spec of `Certificate` resource and latest `CertificateRequest`s as well as the data in `Secret` containing the X.509 certificate. + +The issuance process will always get triggered if the: + +- `Secret` named on `Certificate`'s spec, does not exist, is missing private key or certificate data or contains corrupt data +- private key stored in the `Secret` does not match the private key spec on `Certificate` +- public key of the issued certificate does not match the private key stored in the `Secret` +- cert-manager issuer annotations on the `Secret` do not match the issuer specified on the `Certificate` +- DNS names, IP addresses, URLS or email addresses on the issued certificate do not match those on the `Certificate` spec +- certificate needs to be renewed (because it has expired or the renewal time is now or in the past) +- certificate has been marked for renewal manually [using `cmctl`](../reference/cmctl.md#renew) + +Additionally, if the latest `CertificateRequest` for the `Certificate` is found, cert-manager will also re-issue if: + +- the common name on the CSR found on the `CertificateRequest` does not match that on the `Certificate` spec +- the subject fields on the CSR found on the `CertificateRequest` do not match the subject fields of the `Certificate` spec +- the duration on the `CertificateRequest` does not match the duration on the `Certificate` spec +- `isCA` field value on the `Certificate` spec does not match that on the `CertificateRequest` +- the DNS names, IP addresses, URLS or email addresses on the `CertificateRequest` spec do not match those on the `Certificate` spec +- key usages on the `CertificateRequest` spec do not match those on the `Certificate` spec + +Note that for certain fields re-issuance on change gets triggered only if there +is a `CertificateRequest` that cert-manager can use to determine whether +`Certificate`'s spec has changed since the previous issuance. This is because +some issuers may not respect the requested values for these fields, so we cannot +rely on the values in the issued X.509 certificates. One such field is +`.spec.duration`- change to this field will only trigger re-issuance if there is +a `CertificateRequest` to compare with. In case where you need to re-issue, but +re-issuance does not get triggered automatically due to there being no +`CertificateRequest` (i.e after backup and restore), you can use [`cmctl +renew`](../reference/cmctl.md#renew) to trigger it manually. + +### Why isn't my root certificate in my issued Secret's `tls.crt`? + +Occasionally, people work with systems which have made a flawed choice regarding TLS chains. The [TLS spec](https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.2) +has the following section for the "Server Certificate" section of the TLS handshake: + +> This is a sequence (chain) of certificates. The sender's +> certificate MUST come first in the list. Each following +> certificate MUST directly certify the one preceding it. Because +> certificate validation requires that root keys be distributed +> independently, the self-signed certificate that specifies the root +> certificate authority MAY be omitted from the chain, under the +> assumption that the remote end must already possess it in order to +> validate it in any case. + +In a standard, secure and correctly configured TLS environment, adding a root certificate to the chain is +almost always unnecessary and wasteful. + +There are two ways that a certificate can be trusted: + +- explicitly, by including it in a trust store. +- through a signature, by following the certificate's chain back up to an explicitly trusted certificate. + +Crucially, root certificates are by definition self-signed and they cannot be validated through a signature. + +As such, if we have a client trying to validate the certificate chain sent by the server, the client must already have the +root before the connection is started. If the client already has the root, there was no point in it being sent by the server! + +The same logic with not sending root certificates applies for servers trying to validate client certificates; +the [same justification](https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.6) is given in the TLS RFC. + +### How can I see all the historic events related to a certificate object? + +cert-manager publishes all events to the Kubernetes events mechanism, you can get the events for your specific resources using `kubectl describe `. + +Due to the nature of the Kubernetes event mechanism these will be purged after a while. If you're using a dedicated logging system it might be able or is already also storing Kubernetes events. + +### What happens if issuance fails? Will it be retried? + +{/* This empty link preserves old links to #what-happens-if-a-renewal-doesn't happen?-will-it-be-tried-again-after-some-time?", which matched the old title of this section */} + + + +cert-manager will retry a failed issuance except for a few rare edge cases where +manual intervention is needed. + +We aim to retry after a short delay in case of ephemeral failures such as +network connection errors and with a longer exponentially increasing delay after +'terminal' failures. + +You can observe that latest issuance has terminally failed if the `Certificate` +has `Issuing` condition set to false and has `status.lastFailureTime` set. In +this case the issuance will be retried after an exponentially increasing delay +(1 to 32 hours) by creating a new `CertficateRequest`. You can trigger an +immediate renewal using the [`cmctl renew` +command](../reference/cmctl.md#renew). Terminal failures occur if the issuer +sets the `CertificateRequest` to failed (for example if CA rejected the request +due to a rate limit being reached) or invalid or if the `CertificateRequest` +gets denied by an approver. + +Ephemeral failures result in the same `CertificateRequest` being re-synced after +a short delay (up to 5 minutes). Typically they can only be observed in +cert-manager controller logs. + +If it appears the issuance has got stuck and `cmctl renew` does not work, you +can delete the latest `CertificateRequest`. This is mostly a harmless action +(the worst that could happen is duplicate issuance if there was a potentially +successful one in progress), but we do aim for this to not be part of user flow- +do reach out if you think you have found a case where the flow could be +improved. + +### Is ECC (elliptic-curve cryptography) supported? + +cert-manager supports ECDSA key pairs! You can set your certificate to use ECDSA in the `privateKey` part of your Certificate resource. + +For example: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: ecdsa +spec: + secretName: ecdsa-cert + isCA: false + privateKey: + algorithm: ECDSA + size: 256 + dnsNames: + - ecdsa.example.com + issuerRef: + [...] +``` + +### If `renewBefore` or `duration` is not defined, what will be the default value? + +Default `duration` is [90 days](https://github.com/cert-manager/cert-manager/blob/v1.2.0/pkg/apis/certmanager/v1/const.go#L26). If `renewBefore` has not been set, `Certificate` will be renewed 2/3 through its _actual_ duration. + +## Miscellaneous + +### Kubernetes has a builtin `CertificateSigningRequest` API. Why not use that? + +Kubernetes has a [Certificate Signing Requests API], +and a [`kubectl certificates` command] which allows you to approve certificate signing requests +and have them signed by the certificate authority (CA) of the Kubernetes cluster. + +This API and CLI have occasionally been misused to sign certificates for use by non-control-plane Pods but this is a mistake. +For the security of the Kubernetes cluster, it is important to limit access to the Kubernetes certificate authority, +and it is important that you do not use that certificate authority to sign certificates which are used outside of the control-plane, +because such certificates increase the opportunity for attacks on the Kubernetes API server. + +In Kubernetes 1.19 the [Certificate Signing Requests API] has reached V1 +and it can be used more generally by following (or automating) the [Request Signing Process]. + +cert-manager currently has some [limited experimental support] for this resource. + +[Certificate Signing Requests API]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#certificatesigningrequest-v1-certificates-k8s-io +[`kubectl certificates` command]: https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#certificate +[Request signing process]: https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#request-signing-process +[limited experimental support]: ../usage/kube-csr.md + +### How to write "cert-manager" + +cert-manager should always be written in lowercase. Even when it would normally be +capitalized such as in titles or at the start of sentences. A hyphen should always be +used between the words, don't replace it with a space and don't remove it. diff --git a/content/v1.12-docs/getting-started/README.md b/content/v1.12-docs/getting-started/README.md new file mode 100644 index 0000000000..c358360f49 --- /dev/null +++ b/content/v1.12-docs/getting-started/README.md @@ -0,0 +1,36 @@ +--- +title: Getting Started with cert-manager +description: Quick start guides for cert-manager +--- + + NGINX Ingress Controller icon + Let's Encrypt icon 292Jacob, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons + Learn how to deploy cert-manager and how to configure it to get certificates for the **NGINX Ingress controller** from **Let's Encrypt**. + + + + Google Kubernetes Engine icon + Let's Encrypt icon 292Jacob, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons + Learn how to deploy cert-manager on **Google Kubernetes Engine** and how to configure it to get certificates for Ingress, from **Let's Encrypt**. + + + + Azure Kubernetes Services icon + Let's Encrypt icon 292Jacob, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons + Learn how to deploy cert-manager on **Azure Kubernetes Service (AKS)** and how to configure it to get certificates for an HTTPS web server, from **Let's Encrypt**. + diff --git a/content/v1.12-docs/installation/README.md b/content/v1.12-docs/installation/README.md new file mode 100644 index 0000000000..df829ac3d1 --- /dev/null +++ b/content/v1.12-docs/installation/README.md @@ -0,0 +1,41 @@ +--- +title: Installation +description: Learn about the various ways you can install cert-manager and how to choose between them +--- + +Learn about the various ways you can install cert-manager and how to choose between them. + +## Default static install + +> You don't require any tweaking of the cert-manager install parameters. + +The default static configuration can be installed as follows: + +```bash +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +``` + +📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). + +## Getting started + +> You quickly want to learn how to use cert-manager and what it can be used for. + +📖 **kubectl apply**: For new users we recommend [installing cert-manager using kubectl apply and static manifests](./kubectl.md). + +📖 **helm**: You can [use helm to install cert-manager](./helm.md) and this also allows you to customize the installation if necessary. + +📖 **OperatorHub**: If you have an OpenShift cluster, consider [installing cert-manager via OperatorHub](./operator-lifecycle-manager.md), +which you can do from the OpenShift web console. + +🚧 **cmctl**: Try the [experimental `cmctl x install` command](../reference/cmctl.md#install) to quickly install cert-manager. + +## Continuous deployment + +> You know how to configure your cert-manager setup and want to automate this. + +📖 **helm**: You can use [the cert-manager Helm chart](./helm.md) directly with systems like Flux, ArgoCD and Anthos. + +📖 **helm template**: You can use `helm template` to generate customized cert-manager installation manifests. +See [Output YAML using helm template](./helm.md#output-yaml) for more details. +This templated cert-manager manifest can be piped into your preferred deployment tool. diff --git a/content/v1.12-docs/installation/api-compatibility.md b/content/v1.12-docs/installation/api-compatibility.md new file mode 100644 index 0000000000..e31b96ae29 --- /dev/null +++ b/content/v1.12-docs/installation/api-compatibility.md @@ -0,0 +1,22 @@ +--- +title: API compatibility +description: cert-manager API compatibility guarantees +--- + +cert-manager aims to abide by the same API compatibility policy as upstream Kubernetes APIs as documented in the [Kubernetes Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/#deprecating-parts-of-the-api). + +This is to ensure a smooth upgrade and downgrade experience for users, i.e to make sure that users' cert-manager custom resources keep functioning in the same way +after an upgrade or downgrade of cert-manager. + +In some cases, we may need to require users to take actions before upgrading or may need to diverge from the API compatibility promise but we'll treat this as an absolute +last resort. In general the main criteria by which we'd determine whether a change is acceptable would be user value. + +For example in the event of a truly critical bug, a fix that breaks the API compatibility promise by changing the default behavior of an API field _might_ be acceptable. As of yet, though, there has never been a need for such a change. + +## Alpha / Beta API Versions + +As in upstream Kubernetes, We don't commit to preserving alpha or beta API versions indefinitely. + +In cert-manager v1.7 [all alpha and beta API versions prior to `v1` were removed](https://github.com/cert-manager/cert-manager/pull/4635). + +NB: The Kubernetes deprecation policy notes that API removal introduces an issue with objects stored at the removed versions. To fix this, we wrote a [custom tool](https://cert-manager.io/docs/installation/upgrading/remove-deprecated-apis/) that users could run once to migrate their resources. diff --git a/content/v1.12-docs/installation/best-practice.md b/content/v1.12-docs/installation/best-practice.md new file mode 100644 index 0000000000..f2ca51b603 --- /dev/null +++ b/content/v1.12-docs/installation/best-practice.md @@ -0,0 +1,48 @@ +--- +title: Best Practice +description: Learn how to deploy cert-manager to comply with popular security standards such as those produced by the CIS, NSA, and BSI. +--- + +Learn how to deploy cert-manager to comply with popular security standards such as +the [CIS Kubernetes Benchmark](https://www.cisecurity.org/benchmark/kubernetes/), +the [NSA Kubernetes Hardening Guide](https://media.defense.gov/2022/Aug/29/2003066362/-1/-1/0/CTR_KUBERNETES_HARDENING_GUIDANCE_1.2_20220829.PDF), or +the [BSI Kubernetes Security Recommendations](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Grundschutz/International/bsi_it_gs_comp_2022.pdf?__blob=publicationFile&v=2#page=475). + +## Overview + +The default cert-manager resources in the Helm chart or YAML manifests (Deployment, Pod, ServiceAccount etc) are designed for backwards compatibility rather than for best practice or maximum security. +You may find that the default resources do not comply with the security policy on your Kubernetes cluster +and in that case you can modify the installation configuration using Helm chart values to override the defaults. + +## Restrict Auto-Mount of Service Account Tokens + +This recommendation is described in the [Kyverno Policy Catalogue](https://kyverno.io/policies/other/restrict_automount_sa_token/restrict_automount_sa_token/) as follows: +> Kubernetes automatically mounts ServiceAccount credentials in each Pod. The +> ServiceAccount may be assigned roles allowing Pods to access API resources. +> Blocking this ability is an extension of the least privilege best practice and +> should be followed if Pods do not need to speak to the API server to function. +> This policy ensures that mounting of these ServiceAccount tokens is blocked + +The cert-manager components *do* need to speak to the API server but we still recommend setting `automountServiceAccountToken: false` for the following reasons: +1. Setting `automountServiceAccountToken: false` will allow cert-manager to be installed on clusters where Kyverno (or some other policy system) is configured to deny Pods that have this field set to `true`. The Kubernetes default value is `true`. +2. With `automountServiceAccountToken: true`, *all* the containers in the Pod will mount the ServiceAccount token, including side-car and init containers that might have been injected into the cert-manager Pod resources by Kubernetes admission controllers. + The principle of least privilege suggests that it is better to explicitly mount the ServiceAccount token into the cert-manager containers. + +So it is recommended to set `automountServiceAccountToken: false` and manually add a projected `Volume` to each of the cert-manager Deployment resources, containing the ServiceAccount token, CA certificate and namespace files that would normally be [added automatically by the Kubernetes ServiceAccount controller](https://github.com/kubernetes/kubernetes/blob/3992eda8e61725c470fb6141a7fe4e7f9ee31ea5/plugin/pkg/admission/serviceaccount/admission.go#L421-L460), +and to explicitly add a read-only `VolumeMount` to each of the cert-manager containers. + +An example of this configuration is included in the Helm Chart Values file below. + +## Best Practice Helm Chart Values + +Download the following Helm chart values file and supply it to `helm install`, `helm upgrade`, or `helm template` using the `--values` flag: + +🔗 `values.best-practice.yaml` +```yaml file=../../../public/docs/installation/best-practice/values.best-practice.yaml +``` + +## Other + +This list of recommendations is a work-in-progress. +If you have other best practice recommendations please [contribute to this page](../contributing/contributing-flow.md). + diff --git a/content/v1.12-docs/installation/code-signing.md b/content/v1.12-docs/installation/code-signing.md new file mode 100644 index 0000000000..cd05c04f2f --- /dev/null +++ b/content/v1.12-docs/installation/code-signing.md @@ -0,0 +1,59 @@ +--- +title: cert-manager Signature Verification +description: 'cert-manager installation: Code signing' +--- + +To help prevent [supply chain attacks](https://en.wikipedia.org/wiki/Supply_chain_attack), some cert-manager release +artifacts are cryptographically signed so you can be sure that the version of cert-manager you're about to install +is actually built by and provided by the cert-manager maintainers. + +This signing is vitally important if for any reason you need to use a mirrored version of cert-manager; it allows you +to confirm that the mirror hasn't tampered with the code you're about to install. + +Signing keys required for verification are all available on this website, but the actual key that you need might depend +on the artifact you're trying to validate in the future. At the time of writing, all signing is done using the same underlying +key. + +## Container Images / Cosign + +For all cert-manager versions from `v1.8.0` and later, cert-manager container images are signed and verifiable using [`cosign`](https://docs.sigstore.dev/cosign/overview). + +The simplest way to verify signatures is to download the public key and then pass it to the cosign CLI directly: + +```console +curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem +IMAGE_TAG=v1.11.0 # change as needed +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-controller:$IMAGE_TAG +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-webhook:$IMAGE_TAG +``` + +For a more fully-featured signature verification process in Kubernetes, check out [`connaisseur`](https://sse-secure-systems.github.io/connaisseur/). + +- PEM-encoded public key: [`cert-manager-pubkey-2021-09-20.pem`](https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem) + +## Helm Charts + +
+Helm requires the use of PGP for verification; the key format is different. + +Trying to use "plain" PEM encoded public keys during verification will fail. +
+ +For all cert-manager versions from `v1.6.0` and later, Helm charts are signed and verifiable through the Helm CLI. + +The easiest way to verify is to grab the GPG keyring directly, which can then be passed into `helm verify` like so: + +```console +curl -sSL https://cert-manager.io/public-keys/cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg > cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg +helm verify --keyring cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg /path/to/cert-manager-vx.y.z.tgz +``` + +- GPG keyring: [`cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg`](https://cert-manager.io/public-keys/cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg) + +If you know what you're doing and you want the signing key in a format that's easy to import into GPG, +it's available in an ASCII armored version: + +- ASCII-armored signing key: [`cert-manager-pgp-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.asc`](https://cert-manager.io/public-keys/cert-manager-pgp-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.asc) diff --git a/content/v1.12-docs/installation/compatibility.md b/content/v1.12-docs/installation/compatibility.md new file mode 100644 index 0000000000..516bc941a0 --- /dev/null +++ b/content/v1.12-docs/installation/compatibility.md @@ -0,0 +1,114 @@ +--- +title: Compatibility with Kubernetes Platform Providers +description: 'cert-manager installation: Cloud provider compatibility' +--- + +Below you will find details on various compatibility issues and quirks that you +may be affected by when deploying cert-manager. If you believe we've missed something +please feel free to raise an issue or a pull request with the details! + +
+If you're using AWS Fargate or else if you've specifically configured +cert-manager to run the host's network, be aware that kubelet listens on port +`10250` by default which clashes with the default port for the cert-manager +webhook. + +As such, you'll need to change the webhook's port when setting up cert-manager. + +For installations using Helm, you can set the `webhook.securePort` parameter +when installing cert-manager either using a command line flag or an entry in +your `values.yaml` file. + +If you have a port clash, you could see confusing error messages regarding +untrusted certs. See [#3237](https://github.com/cert-manager/cert-manager/issues/3237) +for more details. +
+ +## GKE + +When Google configure the control plane for private clusters, they automatically +configure VPC peering between your Kubernetes cluster's network and a separate +Google-managed project. + +In order to restrict what Google are able to access within your cluster, the +firewall rules configured restrict access to your Kubernetes pods. This means +that the webhook won't work, and you'll see errors such as +`Internal error occurred: failed calling admission webhook ... the server is +currently unable to handle the request`. + +In order to use the webhook component with a GKE private cluster, you must +configure an additional firewall rule to allow the GKE control plane access to +your webhook pod. + +You can read more information on how to add firewall rules for the GKE control +plane nodes in the [GKE +docs](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules). + + +### GKE Autopilot + +GKE Autopilot mode with Kubernetes < 1.21 does not support cert-manager, +due to a [restriction on mutating admission webhooks](https://github.com/cert-manager/cert-manager/issues/3717). + +As of October 2021, only the "rapid" Autopilot release channel has rolled +out version 1.21 for Kubernetes masters. Installation via the helm chart +may end in an error message but cert-manager is reported to be working by +some users. Feedback and PRs are welcome. + +**Problem**: GKE Autopilot does not allow modifications to the `kube-system`-namespace. + +Historically we've used the `kube-system` namespace to prevent multiple installations of cert-manager in the same cluster. + +Installing cert-manager in these environments with default configuration can cause issues with bootstrapping. +Some signals are: + +* `cert-manager-cainjector` logging errors like: + +```text +E0425 09:04:01.520150 1 leaderelection.go:334] error initially creating leader election record: leases.coordination.k8s.io is forbidden: User "system:serviceaccount:cert-manager:cert-manager-cainjector" cannot create resource "leases" in API group "coordination.k8s.io" in the namespace "kube-system": GKEAutopilot authz: the namespace "kube-system" is managed and the request's verb "create" is denied +``` + +* `cert-manager-startupapicheck` not completing and logging messages like: + +```text +Not ready: the cert-manager webhook CA bundle is not injected yet +``` + +**Solution**: Configure cert-manager to use a different namespace for leader election, like this: + +```console +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version ${CERT_MANAGER_VERSION} --set global.leaderElection.namespace=cert-manager +``` + +The implication for a `kubectl apply` type installation is then either "you must manually update the manifests prior to installation by replacing `kube-system` with cert-manager" or "Don't install cert-manager using kubectl apply. Helm is the recommended solution". + +## AWS EKS + +When using a custom CNI (such as Weave or Calico) on EKS, the webhook cannot be +reached by cert-manager. This happens because the control plane cannot be +configured to run on a custom CNI on EKS, so the CNIs differ between control +plane and worker nodes. + +To address this, the webhook can be run in the host network so it can be reached +by cert-manager, by setting the `webhook.hostNetwork` key to true on your +deployment, or, if using Helm, configuring it in your `values.yaml` file. + +Note that running on the host network will necessitate changing the webhook's +port; see the warning at the top of the page for details. + +### AWS Fargate + +It's worth noting that using AWS Fargate doesn't allow much network configuration and +will cause the webhook's port to clash with the kubelet running on port 10250, as seen +in [#3237](https://github.com/cert-manager/cert-manager/issues/3237). + +When deploying cert-manager on Fargate, you _must_ change the port on which +the webhook listens. See the warning at the top of this page for more details. + +Because Fargate forces you to use its networking, you cannot manually set the networking +type and options such as `webhook.hostNetwork` on the helm chart will cause your +cert-manager deployment to fail in surprising ways. diff --git a/content/v1.12-docs/installation/featureflags.md b/content/v1.12-docs/installation/featureflags.md new file mode 100644 index 0000000000..10548df563 --- /dev/null +++ b/content/v1.12-docs/installation/featureflags.md @@ -0,0 +1,72 @@ +--- +title: Feature flags +description: Using feature gated functionality +--- + +New cert-manager features and functionality are often initially implemented behind a feature gate. This is so as to not break users with functionality that has not yet been tested in production as well as to give us a chance to remove or modify API fields and functionality following user feedback. + +We have alpha and beta features. We do not aim to keep any of the features in alpha or beta stage indefinitely. Feature gating should only be used for functionality that can eventually be turned on by default for all users (or, if it is opt-in, can be safely toggled on by any user with a supported cert-manager installation). + +A feature gate can be toggled on/off using `--feature-gates` flags on cert-manager controller. For feature gated functionality that comes with new API fields there is also a corresponding feature gate on webhook that also needs to be enabled using a `--feature-gates` flag if you want to use it. + +**Alpha** + +All alpha features are off by default. We retain the right to change or remove +alpha features without warning. An API field that is part of an alpha feature +and requires a webhook feature flag to be used also can also be removed from the +API without warning. An alpha feature might not work for all cert-manager's +supported Kubernetes versions. If you want to disable a previously enabled alpha +feature gate, you should make sure that you have updated any resources that have API +fields set that are only valid if the feature gate is enabled, else the resources +will end up in an invalid state. + +**Beta** + +All beta features are off by default. Beta features will not be removed, but may +be changed. If the feature gets changed in incompatible ways, we will provide +migration instructions. A beta feature will work with all cert-manager's +supported Kubernetes versions. If you want to disable a previously enabled beta +feature gate, you should make sure that you have updated any resources that have API +fields set that are only valid if the beta feature gate is on, else the resources +will end up in invalid state. + +**GA** + +A feature that is GA is on by default and cannot be disabled (unless it's opt in and toggled on/off by another mechanism, such as a flag). +With regards to API fields and their functionality, we keep Kubernetes API compatibility promise, see [API compatibility](./api-compatibility.md). +The feature flag for a GA feature might be left in place to avoid breaking folks, but will be non-functional. + +## Graduation + +The graduation criteria can be different for each feature. +Generally, we find user feedback most valuable when determining if a feature is sufficiently mature to graduate. If you are using an alpha or beta feature and would like to see it graduate, it would be great if you could give us some feedback about how you use it and whether you find the API useful to initiate graduation process. Feel free to [open a GitHub issue](https://github.com/cert-manager/cert-manager/issues/new/choose) or [join one of our meetings](../contributing/#meetings) to talk about this. + +## List of current feature gates + +### Alpha + +See `--feature-gates` flags on cert-manager controller and webhook to enable any of these features. + +- `AdditionalCertificateOutputFormats`. Added in cert-manager 1.7.0. Allows to specify additional formats in which cert-manager will store issued certificates and keys. See [release note](../release-notes/release-notes-1.7.md#additional-certificate-output-formats). Requires the feature to be enabled on both cert-manager controller and webhook + +- `ExperimentalCertificateSigningRequestControllers`. Added in cert-manager + 1.4.0. Allows to use Kubernetes + [CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) + resources with cert-manager. See [release notes](../release-notes/release-notes-1.4.md#experimental-support-for-kubernetes-certificatesigningrequests) + +- `ExperimentalGatewayAPISupport`. Added in cert-manager 1.5.0. Allows to use cert-manager to automatically issue certificates for `Gateway` resources as well as use `Gateway`s and `HTTPRoute`s to solve ACME HTTP-01 challenges. See [Securing Gateway resources](../usage/gateway.md) + +- `LiteralCertificateSubject`. Added in cert-manager 1.9.0. Allows to specify certificate subject in a form that can be used to define a location in LDAP directory tree. See [release notes](../release-notes/release-notes-1.9.md#literal-certificate-subjects) + +- `ServerSideApply`. Added in cert-manager 1.8.0. If this feature is enabled, cert-manager uses [Server side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) when creating or updating API resources. This will speed cert-manager operations and prevent the resource version conflict errors. See [release notes](../release-notes/release-notes-1.8.md#server-side-apply) + +- `StableCertificateRequestName`. Added in cert-manager 1.10.0. Will enable generation of `CertificateRequest` resources with a fixed name. See [`cert-manager#5487`](https://github.com/cert-manager/cert-manager/pull/5487) + +- `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.11.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) + +- `ValidateCAA`. Added in cert-manager 0.7.2. CAA checking when issuing a certificate. + + +### Beta + +There are currently no beta feature gates diff --git a/content/v1.12-docs/installation/helm.md b/content/v1.12-docs/installation/helm.md new file mode 100644 index 0000000000..f9b1db130c --- /dev/null +++ b/content/v1.12-docs/installation/helm.md @@ -0,0 +1,207 @@ +--- +title: Helm +description: 'cert-manager installation: Using Helm' +--- + +## Installing with Helm + +cert-manager provides Helm charts as a first-class method of installation on both Kubernetes and OpenShift. + +Be sure never to embed cert-manager as a sub-chart of other Helm charts; cert-manager manages +non-namespaced resources in your cluster and care must be taken to ensure that it is installed exactly once. + +### Prerequisites + +- [Install Helm version 3 or later](https://helm.sh/docs/intro/install/). +- Install a [supported version of Kubernetes or OpenShift](./supported-releases.md). +- Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. + +### Steps + + +#### 1. Add the Helm repository + +This repository is the only supported source of cert-manager charts. There are some other mirrors and copies across the internet, but those are entirely unofficial and could present a security risk. + +Notably, the "Helm stable repository" version of cert-manager is deprecated and should not be used. + +```bash +helm repo add jetstack https://charts.jetstack.io +``` + +#### 2. Update your local Helm chart repository cache: + +```bash +helm repo update +``` + +#### 3. Install `CustomResourceDefinitions` + +cert-manager requires a number of CRD resources, which can be installed manually using `kubectl`, +or using the `installCRDs` option when installing the Helm chart. + +##### Option 1: installing CRDs with `kubectl` + + +```bash +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.crds.yaml +``` + +##### Option 2: install CRDs as part of the Helm release + +To automatically install and manage the CRDs as part of your Helm release, you +must add the `--set installCRDs=true` flag to your Helm installation command. + +Uncomment the relevant line in the next steps to enable this. + +Note that if you're using a `helm` version based on Kubernetes `v1.18` or below (Helm `v3.2`), `installCRDs` will not work with cert-manager `v0.16`. See the [v0.16 upgrade notes](./upgrading/upgrading-0.15-0.16.md#helm) for more details. + +#### 4. Install cert-manager + +To install the cert-manager Helm chart, use the [Helm install command](https://helm.sh/docs/helm/helm_install/) as described below. + +```bash +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.11.0 \ + # --set installCRDs=true +``` + +A full list of available Helm values is on [cert-manager's ArtifactHub page](https://artifacthub.io/packages/helm/cert-manager/cert-manager). + +The example below shows how to tune the cert-manager installation by overwriting the default Helm values: + +```bash +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.11.0 \ + # --set installCRDs=true + --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter + --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter +``` + +Once you have deployed cert-manager, you can [verify](./verify.md) the installation. + +### Installing cert-manager as subchart + +If you have configured cert-manager as a subchart all the components of cert-manager will be installed into the namespace of the helm release you are installing. + +There may be a situation where you want to specify the namespace to install cert-manager different to the umbrella chart's namespace. + +This is a [known issue](https://github.com/helm/helm/issues/5358) with helm and subcharts, that you can't specify the namespace for the subchart and is being solved by most public charts by allowing users to set the namespace via the values file, but needs to be a capability added to the chart by the maintainers. + +This capability is now available in the cert-manager chart and can be set either in the values file or via the `--set` switch. + +#### Example usage + +Below is an example `Chart.yaml` with cert-manager as a subchart + +```yaml +apiVersion: v2 +name: example_chart +description: A Helm chart with cert-manager as subchart +type: application +version: 0.1.0 +appVersion: "0.1.0" +dependencies: + - name: cert-manager + version: v1.11.0 + repository: https://charts.jetstack.io + alias: cert-manager + condition: cert-manager.enabled +``` +You can then override the namespace in 2 ways +1. In `Values.yaml` file +```yaml +cert-manager: #defined by either the name or alias of your dependency in Chart.yaml + namespace: security +``` +2. In the helm command using `--set` +```bash +helm install example example_chart \ + --namespace example \ + --create-namespace \ + --set cert-manager.namespace=security +``` + +The above example will install cert-manager into the security namespace. + +## Output YAML + +Instead of directly installing cert-manager using Helm, a static YAML manifest can be created using the [Helm template command](https://helm.sh/docs/helm/helm_template/). +This static manifest can be tuned by providing the flags to overwrite the default Helm values: + +```bash +helm template \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.11.0 \ + # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter + # --set installCRDs=true \ # Uncomment to also template CRDs + > cert-manager.custom.yaml +``` + +## Uninstalling + +> **Warning**: To uninstall cert-manager you should always use the same process for +> installing but in reverse. Deviating from the following process whether +> cert-manager has been installed from static manifests or Helm can cause issues +> and potentially broken states. Please ensure you follow the below steps when +> uninstalling to prevent this happening. + +Before continuing, ensure that all cert-manager resources that have been created +by users have been deleted. You can check for any existing resources with the +following command: + +```bash +kubectl get Issuers,ClusterIssuers,Certificates,CertificateRequests,Orders,Challenges --all-namespaces +``` + +Once all these resources have been deleted you are ready to uninstall +cert-manager using the procedure determined by how you installed. + +### Uninstalling with Helm + +Uninstalling cert-manager from a `helm` installation is a case of running the +installation process, *in reverse*, using the delete command on both `kubectl` +and `helm`. + + +```bash +helm --namespace cert-manager delete cert-manager +``` + +Next, delete the cert-manager namespace: + +```bash +kubectl delete namespace cert-manager +``` + +Finally, delete the cert-manager +[`CustomResourceDefinitions`](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) +using the link to the version `vX.Y.Z` you installed: +> **Warning**: This command will also remove installed cert-manager CRDs. All +> cert-manager resources (e.g. `certificates.cert-manager.io` resources) will +> be removed by Kubernetes' garbage collector. + +```bash +kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/vX.Y.Z/cert-manager.crds.yaml +``` + +### Namespace Stuck in Terminating State + +If the namespace has been marked for deletion without deleting the cert-manager +installation first, the namespace may become stuck in a terminating state. This +is typically due to the fact that the [`APIService`](https://kubernetes.io/docs/tasks/access-kubernetes-api/setup-extension-api-server) resource still exists +however the webhook is no longer running so is no longer reachable. To resolve +this, ensure you have run the above commands correctly, and if you're still +experiencing issues then run: + +```bash +kubectl delete apiservice v1beta1.webhook.cert-manager.io +``` diff --git a/content/v1.12-docs/installation/kubectl.md b/content/v1.12-docs/installation/kubectl.md new file mode 100644 index 0000000000..742f50768d --- /dev/null +++ b/content/v1.12-docs/installation/kubectl.md @@ -0,0 +1,124 @@ +--- +title: kubectl apply +description: Learn how to install cert-manager using kubectl and static manifests +--- + +Learn how to install cert-manager using kubectl and static manifests. + +## Prerequisites + +- [Install `kubectl` version `>= v1.19.0`](https://kubernetes.io/docs/tasks/tools/). (otherwise, you'll have issues updating the CRDs - see [v0.16 upgrade notes](./upgrading/upgrading-0.15-0.16.md#issue-with-older-versions-of-kubectl)) +- Install a [supported version of Kubernetes or OpenShift](./supported-releases.md). +- Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. + +## Steps + +All resources (the [`CustomResourceDefinitions`](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions) and the cert-manager, cainjector and webhook components) +are included in a single YAML manifest file: + +Install all cert-manager components: + +```bash +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +``` + +By default, cert-manager will be installed into the `cert-manager` +namespace. It is possible to run cert-manager in a different namespace, although +you'll need to make modifications to the deployment manifests. + +Once you have deployed cert-manager, you can [verify the installation](./verify.md). + +## Permissions Errors on Google Kubernetes Engine + +When running on GKE (Google Kubernetes Engine), you might encounter a 'permission denied' error when creating some +of the required resources. This is a nuance of the way GKE handles RBAC and IAM permissions, +and as such you might need to elevate your own privileges to that of a "cluster-admin" **before** +running `kubectl apply`. + +If you have already run `kubectl apply`, you should run it again after elevating your permissions: + +```bash +kubectl create clusterrolebinding cluster-admin-binding \ + --clusterrole=cluster-admin \ + --user=$(gcloud config get-value core/account) +``` + +## Uninstalling +> **Warning**: To uninstall cert-manager you should always use the same process for +> installing but in reverse. Deviating from the following process whether +> cert-manager has been installed from static manifests or Helm can cause issues +> and potentially broken states. Please ensure you follow the below steps when +> uninstalling to prevent this happening. + +Before continuing, ensure that unwanted cert-manager resources that have been created +by users have been deleted. You can check for any existing resources with the +following command: + +```bash +kubectl get Issuers,ClusterIssuers,Certificates,CertificateRequests,Orders,Challenges --all-namespaces +``` +It is recommended that you delete all these resources before uninstalling cert-manager. +If you plan on reinstalling later and don't want to lose some custom resources, you can keep them. +However, this can potentially lead to problems with finalizers. Some resources, like +`Challenges`, should be deleted to avoid [getting stuck in a pending state](#namespace-stuck-in-terminating-state). + +Once the unneeded resources have been deleted, you are ready to uninstall +cert-manager using the procedure determined by how you installed. + +> **Warning**: Uninstalling cert-manager or simply deleting a `Certificate` resource can result in +> TLS `Secret`s being deleted if they have `metadata.ownerReferences` set by cert-manager. +> You can control whether owner references are added to `Secret`s using the `--enable-certificate-owner-ref` controller flag. +> By default, this flag is set to false, which means that no owner references are added. +> However, in cert-manager v1.8 and older, changing the flag's value from true to false _did not_ +> result in existing owner references being removed. This behavior was fixed in cert-manager v1.8. +> Do check the owner references to confirm that they actually are removed. + +### Uninstalling with regular manifests + +Uninstalling from an installation with regular manifests is a case of running +the installation process, *in reverse*, using the delete command of `kubectl`. + +Delete the installation manifests using a link to your currently running version +`vX.Y.Z` like so: +> **Warning**: This command will also remove installed cert-manager CRDs. All +> cert-manager resources (e.g. `certificates.cert-manager.io` resources) will +> be removed by Kubernetes' garbage collector. +> You cannot keep any custom resources if you delete the `CustomResourceDefinition`s. +> If you want to keep resources, you should manage `CustomResourceDefinition`s separately. + +```bash +kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/vX.Y.Z/cert-manager.yaml +``` + +### Namespace Stuck in Terminating State + +If the namespace has been marked for deletion without deleting the cert-manager +installation first, the namespace may become stuck in a terminating state. This +is typically due to the fact that the [`APIService`](https://kubernetes.io/docs/tasks/access-kubernetes-api/setup-extension-api-server) resource still exists +however the webhook is no longer running so is no longer reachable. To resolve +this, ensure you have run the above commands correctly, and if you're still +experiencing issues then run: + +```bash +kubectl delete apiservice v1beta1.webhook.cert-manager.io +``` + +#### Deleting pending challenges + +`Challenge`s can get stuck in a pending state when the finalizer is unable to complete +and Kubernetes is waiting for the cert-manager controller to finish. +This happens when the controller is no longer running to remove the flag, +and the resources are defined as needing to wait. +You can fix this problem by doing what the controller does manually. + +First, delete existing cert-manager webhook configurations, if any: + +```bash +kubectl delete mutatingwebhookconfigurations cert-manager-webhook +``` + +Then change the `.metadata.finalizers` field to an empty list by editing the challenge resource: + +```bash +kubectl edit challenge +``` diff --git a/content/v1.12-docs/installation/operator-lifecycle-manager.md b/content/v1.12-docs/installation/operator-lifecycle-manager.md new file mode 100644 index 0000000000..f9982899ad --- /dev/null +++ b/content/v1.12-docs/installation/operator-lifecycle-manager.md @@ -0,0 +1,243 @@ +--- +title: Operator Lifecycle Manager +description: 'cert-manager installation: Using OLM' +--- + +## Installation managed by OLM + +### Prerequisites + +- Install a [supported version of Kubernetes or OpenShift](./supported-releases.md). +- Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. + +### Option 1: Installing from OperatorHub Web Console on OpenShift + +cert-manager is in the [Red Hat-provided Operator catalog][] called "community-operators". +On OpenShift 4 you can install cert-manager from the [OperatorHub web console][] or from the command line. +These installation methods are described in Red Hat's [Adding Operators to a cluster][] documentation. + +> ⚠️ In cert-manager 1.10 the [secure computing (seccomp) profile](https://kubernetes.io/docs/tutorials/security/seccomp/) for all the Pods +> is set to `RuntimeDefault`. +> On some versions and configurations of OpenShift this can cause the Pod to be rejected by the +> [Security Context Constraints admission webhook](https://docs.openshift.com/container-platform/4.10/authentication/managing-security-context-constraints.html#admission_configuring-internal-oauth). +> +> 📖 Read the [Breaking Changes section in the 1.10 release notes](https://cert-manager.io/docs/release-notes/release-notes-1.10/#on-openshift-the-cert-manager-pods-may-fail-until-you-modify-security-context-constraints) before installing or upgrading from an older version to 1.10 or newer. + +[Red Hat-provided Operator catalog]: https://docs.openshift.com/container-platform/4.7/operators/understanding/olm-rh-catalogs.html#olm-rh-catalogs_olm-rh-catalogs +[OperatorHub web console]: https://docs.openshift.com/container-platform/4.7/operators/understanding/olm-understanding-operatorhub.html +[Adding Operators to a cluster]: https://docs.openshift.com/container-platform/4.7/operators/admin/olm-adding-operators-to-cluster.html + + +### Option 2: Installing from OperatorHub.io + +Browse to the [cert-manager page on OperatorHub.io](https://operatorhub.io/operator/cert-manager), +click the "Install" button and follow the installation instructions. + +### Option 3: Manual install via `kubectl operator` plugin + +[Install OLM][] and [install the `kubectl operator` plugin][] +from the [Krew Kubectl plugins index][] and then use that to install the cert-manager as follows: + +```sh +operator-sdk olm install +kubectl krew install operator +kubectl operator install cert-manager -n operators --channel stable --approval Automatic +``` + +You can monitor the progress of the installation as follows: + +```sh +kubectl get events -w -n operators +``` + +And you can see the status of the installation with: + +```sh +kubectl operator list +``` + +[install OLM]: https://sdk.operatorframework.io/docs/installation/ +[install the `kubectl operator` plugin]: https://github.com/operator-framework/kubectl-operator#install +[Krew Kubectl plugins index]: https://krew.sigs.k8s.io/plugins/#:~:text=cert-manager + +## Release Channels + +Whichever installation method you chose, there will now be an [OLM Subscription resource][] for cert-manager, +tracking the "stable" release channel. E.g. + +```console +$ kubectl get subscription cert-manager -n operators -o yaml +... +spec: + channel: stable + installPlanApproval: Automatic + name: cert-manager +... +status: + currentCSV: cert-manager.v1.7.1 + state: AtLatestKnown +... +``` + +This means that OLM will discover new cert-manager releases in the stable channel, +and, depending on the Subscription settings it will upgrade cert-manager automatically, +when new releases become available. +Read [Manually Approving Upgrades via Subscriptions][] for information about automatic and manual upgrades. + +[OLM Subscription resource]: https://olm.operatorframework.io/docs/concepts/crds/subscription/ +[Manually Approving Upgrades via Subscriptions]: https://olm.operatorframework.io/docs/concepts/crds/subscription/#manually-approving-upgrades-via-subscriptions + +**NOTE:** There is a single release channel called "stable" which will contain all cert-manager releases, shortly after they are released. +In future we may introduce other release channels with alternative release schedules, +in accordance with [OLM's Recommended Channel Naming][]. + +[OLM's Recommended Channel Naming]: https://olm.operatorframework.io/docs/best-practices/channel-naming/#recommended-channel-naming + +## Debugging installation issues + +If you have any issues with your installation, please refer to the +[FAQ](../faq/README.md). + +## Configuration + +The configuration options are quite limited when you install cert-manager using OLM. +There are a few Deployment settings which can be overridden permanently in the Subscription +and most other elements of the cert-manager manifests can be changed by editing the ClusterServiceVersion, +but changes to the ClusterServiceVersion are temporary and will be lost if OLM upgrades cert-manager, +because an upgrade results in a new ClusterServiceVersion resource. + +### Configuration Via Subscription + +When you create an OLM Subscription you can override **some** of the cert-manager Deployment settings, +but the options are quite limited. +The configuration which you add to the Subscription will be applied immediately to the current cert-manager Deployments. +It will also be re-applied if OLM upgrades cert-manager. + +> 🔰 Read the [Configuring Operators deployed by OLM](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/doc/design/subscription-config.md#configuring-operators-deployed-by-olm) design doc in the OLM repository. +> +> 🔰 Refer to the [Subscription API documentation](https://pkg.go.dev/github.com/operator-framework/api@v0.14.0/pkg/operators/v1alpha1#Subscription). + +Here are some examples of configuration that can be achieved by modifying the Subscription resource. +In each case we assume that you are starting with the following [default Subscription from OperatorHub.io](https://operatorhub.io/install/cert-manager.yaml): + +```yaml +# cert-manager.yaml +apiVersion: operators.coreos.com/v1alpha1 +kind: Subscription +metadata: + name: my-cert-manager + namespace: operators +spec: + channel: stable + name: cert-manager + source: operatorhubio-catalog + sourceNamespace: olm +``` + +```bash +kubectl create -f https://operatorhub.io/install/cert-manager.yaml +``` + +#### Change the Resource Requests and Limits + +It is possible to change the resource requests and limits by adding a `config` stanza to the Subscription: + +```yaml +# resources-patch.yaml +spec: + config: + resources: + requests: + memory: "64Mi" + cpu: "250m" + limits: + memory: "128Mi" + cpu: "500m" +``` + + +```bash +kubectl -n operators patch subscription my-cert-manager --type merge --patch-file resources-patch.yaml +``` + +You will see **all** the cert-manager Pods are restarted with the new resources: + +```console +$ kubectl -n operators get pods -o "custom-columns=name:.metadata.name,mem:.spec.containers[*].resources" +name mem +cert-manager-669867589c-n8dcn map[limits:map[cpu:500m memory:128Mi] requests:map[cpu:250m memory:100Mi]] +cert-manager-cainjector-7b7fff8b9c-dxw6b map[limits:map[cpu:500m memory:128Mi] requests:map[cpu:250m memory:100Mi]] +cert-manager-webhook-975bc87b5-tqdj4 map[limits:map[cpu:500m memory:128Mi] requests:map[cpu:250m memory:100Mi]] +``` + +> ⚠️ This configuration will apply to **all** the cert-manager Deployments. +> This is a known limitation of OLM which [does not support configuration of individual Deployments](https://github.com/operator-framework/operator-lifecycle-manager/issues/1794). + +#### Change the NodeSelector + +It is possible to change the `nodeSelector` for cert-manager Pods by adding the following stanza to the Subscription: + +```yaml +# nodeselector-patch.yaml +spec: + config: + nodeSelector: + kubernetes.io/arch: amd64 +``` + +```bash +kubectl -n operators patch subscription my-cert-manager --type merge --patch-file nodeselector-patch.yaml +``` + +You will see **all** the cert-manager Pods are restarted with the new `nodeSelector`: + +```console +$ kubectl -n operators get pods -o "custom-columns=name:.metadata.name,nodeselector:.spec.nodeSelector" +name nodeselector +cert-manager-5b6b8f7d74-k7l94 map[kubernetes.io/arch:amd64 kubernetes.io/os:linux] +cert-manager-cainjector-b89cd6f46-kdkk2 map[kubernetes.io/arch:amd64 kubernetes.io/os:linux] +cert-manager-webhook-8464bc7cc8-64b4w map[kubernetes.io/arch:amd64 kubernetes.io/os:linux] +``` + +> ⚠️ This configuration will apply to **all** the cert-manager Deployments. +> This is a known limitation of OLM which [does not support configuration of individual Deployments](https://github.com/operator-framework/operator-lifecycle-manager/issues/1794). + +### Configuration Via ClusterServiceVersion (CSV) + +The ClusterServiceVersion (CSV) resource contains the templates for all the cert-manager Deployments. +If you patch these templates, OLM will immediately roll out the changes to the Deployments. + +> ⚠️ If OLM upgrades cert-manager your changes will be lost because it will create a new CSV with default Deployment templates. + +Nevertheless, editing (patching) the CSV can be a useful way to override certain cert-manager settings. An example: + +#### Change the log level of cert-manager components + +The following JSON patch will append `-v=6` to command line arguments of the cert-manager controller-manager +(the first container of the first Deployment). + +```bash +kubectl patch csv cert-manager.v1.11.0 \ + --type json \ + -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' +``` + +You will see the controller-manager Pod is restarted with the new arguments. + +```console +$ kubectl -n operators get pods -o "custom-columns=name:.metadata.name,args:.spec.containers[0].args" +name args +cert-manager-797979cbdb-g444r [-v=2 --cluster-resource-namespace=$(POD_NAMESPACE) --leader-election-namespace=kube-system -v=6] +... +``` + +> 🔰 Refer to the [ClusterServiceVersion API documentation](https://pkg.go.dev/github.com/operator-framework/api@v0.14.0/pkg/operators/v1alpha1#ClusterServiceVersion). + +## Uninstall + +Below is the processes for uninstalling cert-manager on OpenShift. + +> ⚠️ To uninstall cert-manager you should always use the same process for +> installing but in reverse. Deviating from the following process can cause +> issues and potentially broken states. Please ensure you follow the below steps +> when uninstalling to prevent this happening. diff --git a/content/v1.12-docs/installation/other-tools.md b/content/v1.12-docs/installation/other-tools.md new file mode 100644 index 0000000000..6d7b0b7044 --- /dev/null +++ b/content/v1.12-docs/installation/other-tools.md @@ -0,0 +1,23 @@ +--- +title: Alternative installation methods +description: 'cert-manager installation: Other tools' +--- + +### kubeprod + +[Bitnami Kubernetes Production +Runtime](https://github.com/bitnami/kube-prod-runtime) (`BKPR`, `kubeprod`) is a +curated collection of the services you would need to deploy on top of your +Kubernetes cluster to enable logging, monitoring, certificate management, +automatic discovery of Kubernetes resources via public DNS servers and other +common infrastructure needs. + +It depends on `cert-manager` for certificate management, and it is [regularly +tested](https://github.com/bitnami/kube-prod-runtime/blob/master/Jenkinsfile) so +the components are known to work together for GKE, AKS, and EKS clusters. For +its ingress stack it creates a DNS entry in the configured DNS zone and requests +a TLS certificate from the Let's Encrypt staging server. + +BKPR can be deployed using the `kubeprod install` command, which will deploy +`cert-manager` as part of it. Details available in the [BKPR installation +guide](https://github.com/bitnami/kube-prod-runtime/blob/master/docs/install.md). \ No newline at end of file diff --git a/content/v1.12-docs/installation/supported-releases.md b/content/v1.12-docs/installation/supported-releases.md new file mode 100644 index 0000000000..930152f250 --- /dev/null +++ b/content/v1.12-docs/installation/supported-releases.md @@ -0,0 +1,289 @@ +--- +title: Supported Releases +description: Supported releases, Kubernetes versions, OpenShift versions and upcoming release timeline +--- + +{/* +Inspired by https://istio.io/latest/about/supported-releases/ +*/} + +This page lists the status, timeline and policy for currently supported releases. + +Each release is supported for a period of four months, and we aim to create a new +release roughly every two months, accounting for holiday periods, major conferences +and other world events. + +cert-manager expects that ServerSideApply is enabled in the cluster for all version of Kubernetes from 1.24 and above. + +

Currently supported releases

+ +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| +| [1.12][] | May 19, 2024 | End of September, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | +| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | + +\*ServerSideApply should be enabled in the cluster + +## Upcoming releases + +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:----------------------:|:---------------------:|:----------------------------------:|:---------------------------------:| +| [1.13][] | End of September, 2024 | End of November, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | + +Dates in the future are uncertain and might change. + +## Old releases + +| Release | Release Date | EOL | Compatible Kubernetes versions | Compatible OpenShift versions | +|----------|:------------:|:------------:|:------------------------------:|:-----------------------------:| +| [1.10][] | Oct 17, 2022 | May 19, 2024 | 1.20 → 1.26 | 4.7 → 4.13 | +| [1.9][] | Jul 22, 2022 | Jan 11, 2023 | 1.20 → 1.24 | 4.7 → 4.11 | +| [1.8][] | Apr 05, 2022 | Oct 17, 2022 | 1.19 → 1.24 | 4.6 → 4.11 | +| [1.7][] | Jan 26, 2021 | Jul 22, 2022 | 1.18 → 1.23 | 4.5 → 4.9 | +| [1.6][] | Oct 26, 2021 | Apr 05, 2022 | 1.17 → 1.22 | 4.4 → 4.9 | +| [1.5][] | Aug 11, 2021 | Jan 26, 2022 | 1.16 → 1.22 | 4.3 → 4.8 | +| [1.4][] | Jun 15, 2021 | Oct 26, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | +| [1.3][] | Apr 08, 2021 | Aug 11, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | +| [1.2][] | Feb 10, 2021 | Jun 15, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | +| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | +| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.11][] | Oct 10, 2019 | Jan 21, 2020 | 1.9 → 1.21 | 3.09 → 4.7 | + +[s]: #kubernetes-supported-versions +[1.13]: https://github.com/cert-manager/cert-manager/milestone/34 +[1.12]: https://cert-manager.io/docs/release-notes/release-notes-1.12 +[1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 +[1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 +[1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 +[1.8]: https://cert-manager.io/docs/release-notes/release-notes-1.8 +[1.7]: https://cert-manager.io/docs/release-notes/release-notes-1.7 +[1.6]: https://cert-manager.io/docs/release-notes/release-notes-1.6 +[1.5]: https://cert-manager.io/docs/release-notes/release-notes-1.5 +[1.4]: https://cert-manager.io/docs/release-notes/release-notes-1.4 +[1.3]: https://cert-manager.io/docs/release-notes/release-notes-1.3 +[1.2]: https://cert-manager.io/docs/release-notes/release-notes-1.2 +[1.1]: https://cert-manager.io/docs/release-notes/release-notes-1.1 +[1.0]: https://cert-manager.io/docs/release-notes/release-notes-1.0 +[0.16]: https://cert-manager.io/docs/release-notes/release-notes-0.16 +[0.15]: https://cert-manager.io/docs/release-notes/release-notes-0.15 +[0.14]: https://cert-manager.io/docs/release-notes/release-notes-0.14 +[0.13]: https://cert-manager.io/docs/release-notes/release-notes-0.13 +[0.12]: https://cert-manager.io/docs/release-notes/release-notes-0.12 +[0.11]: https://cert-manager.io/docs/release-notes/release-notes-0.11 + +We list cert-manager releases on [GitHub](https://github.com/cert-manager/cert-manager/releases), +and release notes on [cert-manager.io](https://cert-manager.io/docs/release-notes/). + +We also maintain detailed [upgrade instructions](https://cert-manager.io/docs/installation/upgrading/). + +## Support policy + +### What we mean by support + +Our support window is four months for each release branch. In the below +diagram, `release-1.2` is an example of a release branch. The support +window corresponds to the two latest releases, given that we produce a new +final release every two months. We offer two types of support: + +- [Technical support](#technical-support), +- [Security and bug fixes](#bug-fixes-support). + +For example, imagining that the latest release is `v1.2.0`, you can expect +support for both `v1.2.0` and `v1.1.0`. Only the last patch release of each +branch is actually supported. + +```diagram + v1.0.0 ^ + Sep 2, 2020 | UNSUPPORTED +------+---------------------------------------------> release-1.0 | RELEASES + \ v + \ + \ v1.1.0 + \ Nov 24, 2020 ^ + ---------+-------------------------------> release-1.1 | + \ | SUPPORTED + \ | RELEASES + \ v1.2.0 | = the two + \ Feb 10, 2021 | last + ------------+--------------> release-1.2 | releases + \ v + \ + \ + \ + -----------> master branch + April 1, 2021 +``` + +

Technical support

+ +Technical assistance is offered on a best-effort basis for supported +releases only. You can request support from the community on [Kubernetes +Slack](https://slack.k8s.io/) (in the `#cert-manager` channel), using +[GitHub Discussions][discussions] or using the [cert-manager-dev][group] +Google group. + +[discussions]: https://github.com/cert-manager/cert-manager/discussions +[group]: https://groups.google.com/g/cert-manager-dev + +

Security and bug fixes

+ +We back-port important bug fixes — including security fixes — to all +currently supported releases. + +- [Security issues](#security-issues), +- [Critical bugs](#critical-bugs), +- [Long-standing bugs](#long-standing-bugs). + +

Security issues

+ +**Security issues** are fixed as soon as possible. They get back-ported to +the last two releases, and a new patch release is immediately created for them. + +

Critical bugs

+ +**Critical bugs** include both regression bugs as well as upgrade bugs. + +Regressions are functionalities that worked in a previous release but no longer +work. [#4142][], [#3393][] and [#2857][] are three examples of regressions. + +Upgrade bugs are issues (often Helm-related) preventing users from +upgrading to currently supported releases from earlier releases of +cert-manager. [#3882][] and [#3644][] are examples of upgrade bugs. + +Note that [intentional breaking changes](#breaking-changes) do not belong to +this category. + +Fixes for critical bugs are (usually) immediately back-ported by creating a new +patch release for the currently supported releases. + +

Long-standing bugs

+ +**Long-standing bug**: sometimes a bug exists for a long time, and may have +known workarounds. [#3444][] is an example of a long-standing bug. + +Where we feel that back-porting would be difficult or might be a stability +risk to clusters running cert-manager, we'll make the fix in a major +release but avoid back-porting the fix. + +

Breaking changes

+ +Breaking changes are changes that intentionally break the cert-manager +Kubernetes API or the command line flags. We avoid making breaking changes +where possible, and where they're required we'll give as much notice as +possible. + +

Other back-ports

+ +We aim to be conservative in what we back-port. That applies especially for anything which +could be a _runtime_ change - that is, a change which might alter behavior for someone +upgrading between patch releases. + +That means that if a candidate for back-porting has a chance of having a runtime impact we're +unlikely to accept the change unless it addresses a security issue or a critical bug. + +We reserve the right to back-port other changes which are unlikely to have a runtime impact, such as +documentation or tooling changes. An example would be [#5209][] which updated how we perform a release of +cert-manager but didn't have any realistic chance of having a runtime impact. + +Generally we'll seek to be pragmatic. A rule of thumb might be to ask: + +"Does this back-port improve cert-manager, bearing in mind that we really value stability for already-released versions?" + +[#3393]: https://github.com/cert-manager/cert-manager/issues/3393 "Broken CloudFlare DNS01 challenge" +[#2857]: https://github.com/cert-manager/cert-manager/issues/2857 "CloudDNS DNS01 challenge crashes cert-manager" +[#4142]: https://github.com/cert-manager/cert-manager/issues/4142 "Cannot issue a certificate that has the same subject and issuer" +[#3444]: https://github.com/cert-manager/cert-manager/issues/3444 "Certificates do not get immediately updated after updating them" +[#3882]: https://github.com/cert-manager/cert-manager/pull/3882 "Certificate's revision history limit validated by webhook" +[#3644]: https://github.com/cert-manager/cert-manager/issues/3644 "Helm upgrade from v1.2 to v1.2 impossible due to a Helm bug" +[#5209]: https://github.com/cert-manager/cert-manager/pull/5209 "release-1.8: rclone" + + +

How we determine supported Kubernetes versions

+ +The list of supported Kubernetes versions displayed in the [Supported Releases](#supported-releases) section +depends on what the cert-manager maintainers think is reasonable to support and to test. + +In practice, this is largely determined based on what versions of [kind](https://github.com/kubernetes-sigs/kind) +are available for testing, and which versions of Kubernetes are provided by major upstream cloud Kubernetes vendors +including EKS, GKE, AKS and OpenShift. + +| Vendor | Oldest Kubernetes Release\* | Other Older Kubernetes Releases | +|:-----------------:|-----------------------------|------------------------------------------------------------------------------------| +| [EKS][eks] | 1.22 (EOL Jun 2023) | 1.23 (EOL Oct 2023), 1.24 (EOL Jan 2024), 1.25 (EOL May 2024), 1.26 (EOL Jun 2024) | +| [GKE][gke] | 1.23 (EOL Jul 2023) | 1.24 (EOL Oct 2023), 1.25 (EOL Feb 2024), 1.26 (EOL May 2024), 1.27 (EOL Jan 2025) | +| [AKS][aks] | 1.24 (EOL Jul 2023) | 1.25 (EOL Dec 2023), 1.26 (EOL Mar 2024), 1.27 (EOL Jun 2024) | +| [OpenShift 4][os] | 1.22 (4.9, EOL Jun 2023) | 1.23 (4.10, EOL Oct 2023), 1.24 (4.11, EOL Feb 2024), 1.25 (4.12, EOL Jan 2025) | + +\*Oldest release relevant to the next cert-manager release, as of 2023-05-19 + +[eks]: https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html#kubernetes-release-calendar +[gke]: https://cloud.google.com/kubernetes-engine/docs/release-schedule +[aks]: https://docs.microsoft.com/en-us/azure/aks/supported-kubernetes-versions#aks-kubernetes-release-calendar +[os]: https://access.redhat.com/support/policy/updates/openshift#dates + +### OpenShift + +cert-manager supports versions of OpenShift 4 based on the version of Kubernetes +that each version maps to. + +For convenience, the following table shows these version mappings: + +| OpenShift versions | Kubernetes version | +|--------------------|--------------------| +| 4.14 | 1.27 | +| 4.13 | 1.26 | +| 4.12 | 1.25 | +| 4.11 | 1.24 | +| 4.10, 4.10 EUS | 1.23 | +| 4.9 | 1.22 | +| 4.8, 4.8 EUS | 1.21 | +| 4.7 | 1.20 | +| 4.6, 4.6 EUS | 1.19 | + +Note that some OpenShift versions listed above may be predicted, since an updated version of OpenShift may +not yet be available for the latest Kubernetes releases. + +The last version of cert-manager to support OpenShift 3 was cert-manager 1.2, which is +no longer maintained. + +## Terminology + +The term "release" (or "minor release") refers to one minor version of +cert-manager. For example, 1.2 and 1.3 are two releases. Note that we do +not use the prefix `v` for releases (just "1.2"). This is because releases +are not used as git tags. + +Patch releases use the `v` prefix (e.g., `v1.2.0`, `v1.3.1`...) since one +patch release = one git tag. The initial patch release is called "final +release": + +| Type of release | Example of git tag | Corresponding release | Corresponding release branch\* | +| --------------- | ------------------ | --------------------- | ------------------------------ | +| Final release | `v1.3.0` | 1.3 | `release-1.3` | +| Patch release | `v1.3.1` | 1.3 | `release-1.3` | +| Pre-release | `v1.4.0-alpha.0` | N/A\*\* | `release-1.4` | + +\*For maintainers: each release has an associated long-lived branch that we +call the “release branch”. For example, `release-1.2` is the release branch +for release 1.2. + +\*\*Pre-releases (e.g., `v1.3.0-alpha.0`) don't have a corresponding +release (e.g., 1.3) since a release only exists after a final release +(e.g., `v1.3.0`) has been created. + +Our naming scheme mostly follows [Semantic Versioning +2.0.0](https://semver.org/) with `v` prepended to git tags and docker +images: + +```plain +v.. +``` + +where `` is increased for each release, and `` counts the +number of patches for the current `` release. A patch is usually a +small change relative to the `` release. diff --git a/content/v1.12-docs/installation/uninstall.md b/content/v1.12-docs/installation/uninstall.md new file mode 100644 index 0000000000..de06fbc9e7 --- /dev/null +++ b/content/v1.12-docs/installation/uninstall.md @@ -0,0 +1,14 @@ +--- +title: Uninstall +description: 'cert-manager installation: Uninstalling cert-manager' +--- + +cert-manager supports running on [Kubernetes](https://kubernetes.io) and +[OpenShift](https://www.openshift.com). The uninstallation process between the +two platforms is similar. Select the method that was used for installing +cert-manager to go to the relevant uninstall documentation. + +- [kubectl](./kubectl.md#uninstalling) +- [helm](./helm.md#uninstalling) + +If you need to preserve cert-manager custom resources (`Certificate`s, `Issuer`s etc), that are not version controlled or backed up by other means, take a look at our [backup and restore guide](../tutorials/backup.md). diff --git a/content/v1.12-docs/installation/verify.md b/content/v1.12-docs/installation/verify.md new file mode 100644 index 0000000000..4a169f1c95 --- /dev/null +++ b/content/v1.12-docs/installation/verify.md @@ -0,0 +1,122 @@ +--- +title: Verifying the Installation +description: 'cert-manager installation: Verifying an upgrade was successful' +--- + +## Check cert-manager API + +First, make sure that [cmctl is installed](../reference/cmctl.md#installation). + +cmctl performs a dry-run certificate creation check against the Kubernetes cluster. +If successful, the message `The cert-manager API is ready` is displayed. + +```bash +$ cmctl check api +The cert-manager API is ready +``` + +The command can also be used to wait for the check to be successful. +Here is an output example of running the command at the same time that cert-manager is being installed: + +```bash +$ cmctl check api --wait=2m +Not ready: the cert-manager CRDs are not yet installed on the Kubernetes API server +Not ready: the cert-manager CRDs are not yet installed on the Kubernetes API server +Not ready: the cert-manager webhook deployment is not ready yet +Not ready: the cert-manager webhook deployment is not ready yet +Not ready: the cert-manager webhook deployment is not ready yet +Not ready: the cert-manager webhook deployment is not ready yet +The cert-manager API is ready +``` + +## Manual verification + +Once you've installed cert-manager, you can verify it is deployed correctly by +checking the `cert-manager` namespace for running pods: + +```bash +$ kubectl get pods --namespace cert-manager + +NAME READY STATUS RESTARTS AGE +cert-manager-5c6866597-zw7kh 1/1 Running 0 2m +cert-manager-cainjector-577f6d9fd7-tr77l 1/1 Running 0 2m +cert-manager-webhook-787858fcdb-nlzsq 1/1 Running 0 2m +``` + +You should see the `cert-manager`, `cert-manager-cainjector`, and +`cert-manager-webhook` pods in a `Running` state. The webhook might take a +little longer to successfully provision than the others. + +If you experience problems, first check the [FAQ](../faq/README.md). + +Create an `Issuer` to test the webhook works okay. +```bash +$ cat < test-resources.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: cert-manager-test +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: test-selfsigned + namespace: cert-manager-test +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: selfsigned-cert + namespace: cert-manager-test +spec: + dnsNames: + - example.com + secretName: selfsigned-cert-tls + issuerRef: + name: test-selfsigned +EOF +``` + +Create the test resources. +```bash +$ kubectl apply -f test-resources.yaml +``` + +Check the status of the newly created certificate. You may need to wait a few +seconds before cert-manager processes the certificate request. +```bash +$ kubectl describe certificate -n cert-manager-test + +... +Spec: + Common Name: example.com + Issuer Ref: + Name: test-selfsigned + Secret Name: selfsigned-cert-tls +Status: + Conditions: + Last Transition Time: 2019-01-29T17:34:30Z + Message: Certificate is up to date and has not expired + Reason: Ready + Status: True + Type: Ready + Not After: 2019-04-29T17:34:29Z +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CertIssued 4s cert-manager Certificate issued successfully +``` + +Clean up the test resources. +```bash +$ kubectl delete -f test-resources.yaml +``` + +If all the above steps have completed without error, you're good to go! + +## Community-maintained tool + +Alternatively, to automatically check if cert-manager is correctly configured, +you can run the community-maintained [cert-manager-verifier](https://github.com/alenkacz/cert-manager-verifier) tool. diff --git a/content/v1.12-docs/manifest.json b/content/v1.12-docs/manifest.json new file mode 100644 index 0000000000..e8b2685a6e --- /dev/null +++ b/content/v1.12-docs/manifest.json @@ -0,0 +1,758 @@ +{ + "routes": [ + { + "title": "cert-manager", + + "routes": [ + { + "title": "Introduction", + "path": "/docs/README.md" + }, + { + "title": "Getting Started", + "path": "/docs/getting-started/README.md" + }, + { + "title": "Installation", + "routes": [ + { + "title": "Introduction", + "path": "/docs/installation/README.md" + }, + { + "title": "Supported Releases", + "path": "/docs/installation/supported-releases.md" + }, + { + "title": "Cloud Compatibility", + "path": "/docs/installation/compatibility.md" + }, + { + "title": "kubectl apply", + "path": "/docs/installation/kubectl.md" + }, + { + "title": "Helm", + "path": "/docs/installation/helm.md" + }, + { + "title": "OperatorHub (OLM)", + "path": "/docs/installation/operator-lifecycle-manager.md" + }, + { + "title": "Other tools", + "path": "/docs/installation/other-tools.md" + }, + { + "title": "Verifying", + "path": "/docs/installation/verify.md" + }, + { + "title": "Feature flags", + "path": "/docs/installation/featureflags.md" + }, + { + "title": "Upgrading", + "routes": [ + { + "title": "Introduction", + "path": "/docs/installation/upgrading/README.md" + }, + { + "title": "Notes on Ingress Class Compatibility", + "path": "/docs/installation/upgrading/ingress-class-compatibility.md" + }, + { + "title": "Migrating Deprecated API Resources", + "path": "/docs/installation/upgrading/remove-deprecated-apis.md" + }, + { + "title": "v1.10 to v1.11", + "path": "/docs/installation/upgrading/upgrading-1.10-1.11.md" + }, + { + "title": "v1.9 to v1.10", + "path": "/docs/installation/upgrading/upgrading-1.9-1.10.md" + }, + { + "title": "v1.8 to v1.9", + "path": "/docs/installation/upgrading/upgrading-1.8-1.9.md" + }, + { + "title": "v1.7 to v1.8", + "path": "/docs/installation/upgrading/upgrading-1.7-1.8.md" + }, + { + "title": "v1.6 to v1.7", + "path": "/docs/installation/upgrading/upgrading-1.6-1.7.md" + }, + { + "title": "v1.5 to v1.6", + "path": "/docs/installation/upgrading/upgrading-1.5-1.6.md" + }, + { + "title": "v1.4 to v1.5", + "path": "/docs/installation/upgrading/upgrading-1.4-1.5.md" + }, + { + "title": "v1.3 to v1.4", + "path": "/docs/installation/upgrading/upgrading-1.3-1.4.md" + }, + { + "title": "v1.2 to v1.3", + "path": "/docs/installation/upgrading/upgrading-1.2-1.3.md" + }, + { + "title": "v1.1 to v1.2", + "path": "/docs/installation/upgrading/upgrading-1.1-1.2.md" + }, + { + "title": "v1.0 to v1.1", + "path": "/docs/installation/upgrading/upgrading-1.0-1.1.md" + }, + { + "title": "v0.16 to v1.0", + "path": "/docs/installation/upgrading/upgrading-0.16-1.0.md" + }, + { + "title": "v0.15 to v0.16", + "path": "/docs/installation/upgrading/upgrading-0.15-0.16.md" + }, + { + "title": "v0.14 to v0.15", + "path": "/docs/installation/upgrading/upgrading-0.14-0.15.md" + }, + { + "title": "v0.13 to v0.14", + "path": "/docs/installation/upgrading/upgrading-0.13-0.14.md" + }, + { + "title": "v0.12 to v0.13", + "path": "/docs/installation/upgrading/upgrading-0.12-0.13.md" + }, + { + "title": "v0.11 to v0.12", + "path": "/docs/installation/upgrading/upgrading-0.11-0.12.md" + }, + { + "title": "v0.10 to v0.11", + "path": "/docs/installation/upgrading/upgrading-0.10-0.11.md" + }, + { + "title": "v0.9 to v0.10", + "path": "/docs/installation/upgrading/upgrading-0.9-0.10.md" + }, + { + "title": "v0.8 to v0.9", + "path": "/docs/installation/upgrading/upgrading-0.8-0.9.md" + }, + { + "title": "v0.7 to v0.8", + "path": "/docs/installation/upgrading/upgrading-0.7-0.8.md" + }, + { + "title": "v0.6 to v0.7", + "path": "/docs/installation/upgrading/upgrading-0.6-0.7.md" + }, + { + "title": "v0.5 to v0.6", + "path": "/docs/installation/upgrading/upgrading-0.5-0.6.md" + }, + { + "title": "v0.4 to v0.5", + "path": "/docs/installation/upgrading/upgrading-0.4-0.5.md" + }, + { + "title": "v0.3 to v0.4", + "path": "/docs/installation/upgrading/upgrading-0.3-0.4.md" + }, + { + "title": "v0.2 to v0.3", + "path": "/docs/installation/upgrading/upgrading-0.2-0.3.md" + } + ] + }, + { + "title": "Uninstall", + "path": "/docs/installation/uninstall.md" + }, + { + "title": "API compatibility", + "path": "/docs/installation/api-compatibility.md" + }, + { + "title": "Signature Verification", + "path": "/docs/installation/code-signing.md" + }, + { + "title": "Best Practice", + "path": "/docs/installation/best-practice.md" + } + ] + }, + { + "title": "Configuration", + "routes": [ + { + "title": "Introduction", + "path": "/docs/configuration/README.md" + }, + { + "title": "SelfSigned", + "path": "/docs/configuration/selfsigned.md" + }, + { + "title": "CA", + "path": "/docs/configuration/ca.md" + }, + { + "title": "Vault", + "path": "/docs/configuration/vault.md" + }, + { + "title": "Venafi", + "path": "/docs/configuration/venafi.md" + }, + { + "title": "External", + "path": "/docs/configuration/external.md" + }, + { + "title": "ACME", + "routes": [ + { + "title": "Introduction", + "path": "/docs/configuration/acme/README.md" + }, + { + "title": "HTTP01", + "routes": [ + { + "title": "Introduction", + "path": "/docs/configuration/acme/http01/README.md" + }, + { + "title": "External Load Balancer", + "path": "/docs/configuration/acme/http01/externalloadbalancer.md" + } + ] + }, + { + "title": "DNS01", + "routes": [ + { + "title": "Introduction", + "path": "/docs/configuration/acme/dns01/README.md" + }, + { + "title": "ACMEDNS", + "path": "/docs/configuration/acme/dns01/acme-dns.md" + }, + { + "title": "Akamai", + "path": "/docs/configuration/acme/dns01/akamai.md" + }, + { + "title": "AzureDNS", + "path": "/docs/configuration/acme/dns01/azuredns.md" + }, + { + "title": "Cloudflare", + "path": "/docs/configuration/acme/dns01/cloudflare.md" + }, + { + "title": "DigitalOcean", + "path": "/docs/configuration/acme/dns01/digitalocean.md" + }, + { + "title": "Google CloudDNS", + "path": "/docs/configuration/acme/dns01/google.md" + }, + { + "title": "RFC-2136", + "path": "/docs/configuration/acme/dns01/rfc2136.md" + }, + { + "title": "Route53", + "path": "/docs/configuration/acme/dns01/route53.md" + }, + { + "title": "Webhook", + "path": "/docs/configuration/acme/dns01/webhook.md" + } + ] + } + ] + } + ] + }, + { + "title": "Usage", + "routes": [ + { + "title": "Introduction", + "path": "/docs/usage/README.md" + }, + { + "title": "Certificate Resources", + "path": "/docs/usage/certificate.md" + }, + { + "title": "Prometheus Metrics", + "path": "/docs/usage/prometheus-metrics.md" + }, + { + "title": "Securing Ingress Resources", + "path": "/docs/usage/ingress.md" + }, + { + "title": "Securing Gateway Resources", + "path": "/docs/usage/gateway.md" + }, + { + "title": "Securing Istio Service Mesh", + "path": "/docs/usage/istio.md" + }, + { + "title": "CSI Driver", + "path": "/docs/usage/csi.md" + }, + { + "title": "Kubernetes CertificateSigningRequests", + "path": "/docs/usage/kube-csr.md" + }, + { + "title": "Policy for cert-manager certificates", + "path": "/docs/usage/approver-policy.md" + } + ] + }, + { + "title": "Projects", + "routes": [ + { + "title": "Contents", + "path": "/docs/projects/README.md" + }, + { + "title": "istio-csr", + "path": "/docs/projects/istio-csr.md" + }, + { + "title": "csi-driver", + "path": "/docs/projects/csi-driver.md" + }, + { + "title": "csi-driver-spiffe", + "path": "/docs/projects/csi-driver-spiffe.md" + }, + { + "title": "approver-policy", + "routes": [ + { + "title": "Introduction", + "path": "/docs/projects/approver-policy/README.md" + }, + { + "title": "API Reference", + "path": "/docs/projects/approver-policy/api-reference.md" + } + ] + }, + { + "title": "trust-manager", + "routes": [ + { + "title": "Introduction", + "path": "/docs/projects/trust-manager/README.md" + }, + { + "title": "API Reference", + "path": "/docs/projects/trust-manager/api-reference.md" + } + ] + } + ] + }, + { + "title": "Tutorials", + "routes": [ + { + "title": "Introduction", + "path": "/docs/tutorials/README.md" + }, + { + "title": "Securing NGINX-ingress", + "path": "/docs/tutorials/acme/nginx-ingress.md" + }, + { + "title": "GKE + Ingress + Let's Encrypt", + "path": "/docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md" + }, + { + "title": "AKS + LoadBalancer + Let's Encrypt", + "path": "/docs/tutorials/getting-started-aks-letsencrypt/README.md" + }, + { + "title": "Migrating from Kube-LEGO", + "path": "/docs/tutorials/acme/migrating-from-kube-lego.md" + }, + { + "title": "Backup and Restore Resources", + "path": "/docs/tutorials/backup.md" + }, + { + "title": "DNS Validation", + "path": "/docs/tutorials/acme/dns-validation.md" + }, + { + "title": "HTTP Validation", + "path": "/docs/tutorials/acme/http-validation.md" + }, + { + "title": "Pomerium Ingress", + "path": "/docs/tutorials/acme/pomerium-ingress.md" + }, + { + "title": "EKS + Ingress + Venafi", + "path": "/docs/tutorials/venafi/venafi.md" + }, + { + "title": "Securing the istio Service Mesh using cert-manager", + "path": "/docs/tutorials/istio-csr/istio-csr.md" + }, + { + "title": "Syncing Secrets Across Namespaces", + "path": "/docs/tutorials/syncing-secrets-across-namespaces.md" + }, + { + "title": "Securing Ingresses with ZeroSSL", + "path": "/docs/tutorials/zerossl/zerossl.md" + } + ] + }, + { + "title": "Troubleshooting", + "routes": [ + { + "title": "Introduction", + "path": "/docs/troubleshooting/README.md" + }, + { + "title": "Troubleshooting ACME / Let's Encrypt Certificates", + "path": "/docs/troubleshooting/acme.md" + }, + { + "title": "Troubleshooting webhook", + "path": "/docs/troubleshooting/webhook.md" + } + ] + }, + { + "title": "FAQ", + "path": "/docs/faq/README.md" + }, + { + "title": "Contributing", + "routes": [ + { + "title": "Introduction", + "path": "/docs/contributing/README.md" + }, + { + "title": "Feature Policy", + "path": "/docs/contributing/policy.md" + }, + { + "title": "Building cert-manager", + "path": "/docs/contributing/building.md" + }, + { + "title": "Contributing Flow", + "path": "/docs/contributing/contributing-flow.md" + }, + { + "title": "CRDs", + "path": "/docs/contributing/crds.md" + }, + { + "title": "DNS Providers", + "path": "/docs/contributing/dns-providers.md" + }, + { + "title": "Running End-to-End Tests", + "path": "/docs/contributing/e2e.md" + }, + { + "title": "Implementing External Issuers", + "path": "/docs/contributing/external-issuers.md" + }, + { + "title": "DCO Sign Off", + "path": "/docs/contributing/sign-off.md" + }, + { + "title": "Release Process", + "path": "/docs/contributing/release-process.md" + }, + { + "title": "Developing with Kind", + "path": "/docs/contributing/kind.md" + }, + { + "title": "Implementing Feature Gates", + "path": "/docs/contributing/featuregates.md" + }, + { + "title": "Google Season of Docs", + "routes": [ + { + "title": "Introduction", + "path": "/docs/contributing/google-season-of-docs/README.md" + }, + { + "title": "2022", + "routes": [ + { + "title": "Introduction", + "path": "/docs/contributing/google-season-of-docs/2022/README.md" + }, + { + "title": "Improve the Navigation and Structure of the cert-manager Website", + "path": "/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md" + } + ] + } + ] + }, + { + "title": "Reporting Security Issues", + "path": "/docs/contributing/security.md" + }, + { + "title": "Coding Conventions", + "path": "/docs/contributing/coding-conventions.md" + }, + { + "title": "Third Party Code Donations", + "path": "/docs/contributing/third-party-code-donation.md" + }, + { + "title": "Signing Keys", + "path": "/docs/contributing/signing-keys.md" + }, + { + "title": "Importing cert-manager in Go", + "path": "/docs/contributing/importing.md" + } + ] + }, + { + "title": "Release Notes", + "routes": [ + { + "title": "Introduction", + "path": "/docs/release-notes/README.md" + }, + { + "title": "v1.11", + "path": "/docs/release-notes/release-notes-1.11.md" + }, + { + "title": "v1.10", + "path": "/docs/release-notes/release-notes-1.10.md" + }, + { + "title": "v1.9", + "path": "/docs/release-notes/release-notes-1.9.md" + }, + { + "title": "v1.8", + "path": "/docs/release-notes/release-notes-1.8.md" + }, + { + "title": "v1.7", + "path": "/docs/release-notes/release-notes-1.7.md" + }, + { + "title": "v1.6", + "path": "/docs/release-notes/release-notes-1.6.md" + }, + { + "title": "v1.5", + "path": "/docs/release-notes/release-notes-1.5.md" + }, + { + "title": "v1.4", + "path": "/docs/release-notes/release-notes-1.4.md" + }, + { + "title": "v1.3", + "path": "/docs/release-notes/release-notes-1.3.md" + }, + { + "title": "v1.2", + "path": "/docs/release-notes/release-notes-1.2.md" + }, + { + "title": "v1.1", + "path": "/docs/release-notes/release-notes-1.1.md" + }, + { + "title": "v1.0", + "path": "/docs/release-notes/release-notes-1.0.md" + }, + { + "title": "v0.16", + "path": "/docs/release-notes/release-notes-0.16.md" + }, + { + "title": "v0.15", + "path": "/docs/release-notes/release-notes-0.15.md" + }, + { + "title": "v0.14", + "path": "/docs/release-notes/release-notes-0.14.md" + }, + { + "title": "v0.13", + "path": "/docs/release-notes/release-notes-0.13.md" + }, + { + "title": "v0.12", + "path": "/docs/release-notes/release-notes-0.12.md" + }, + { + "title": "v0.11", + "path": "/docs/release-notes/release-notes-0.11.md" + }, + { + "title": "v0.10", + "path": "/docs/release-notes/release-notes-0.10.md" + }, + { + "title": "v0.9", + "path": "/docs/release-notes/release-notes-0.9.md" + }, + { + "title": "v0.8", + "path": "/docs/release-notes/release-notes-0.8.md" + }, + { + "title": "v0.7", + "path": "/docs/release-notes/release-notes-0.7.md" + }, + { + "title": "v0.6", + "path": "/docs/release-notes/release-notes-0.6.md" + }, + { + "title": "v0.5", + "path": "/docs/release-notes/release-notes-0.5.md" + }, + { + "title": "v0.4", + "path": "/docs/release-notes/release-notes-0.4.md" + }, + { + "title": "v0.3", + "path": "/docs/release-notes/release-notes-0.3.md" + }, + { + "title": "v0.2", + "path": "/docs/release-notes/release-notes-0.2.md" + }, + { + "title": "v0.1", + "path": "/docs/release-notes/release-notes-0.1.md" + } + ] + }, + { + "title": "Concepts", + "routes": [ + { + "title": "Introduction", + "path": "/docs/concepts/README.md" + }, + { + "title": "Issuer", + "path": "/docs/concepts/issuer.md" + }, + { + "title": "Certificate", + "path": "/docs/concepts/certificate.md" + }, + { + "title": "CertificateRequest", + "path": "/docs/concepts/certificaterequest.md" + }, + { + "title": "ACME Orders and Challenges", + "path": "/docs/concepts/acme-orders-challenges.md" + }, + { + "title": "Webhook", + "path": "/docs/concepts/webhook.md" + }, + { + "title": "CA Injector", + "path": "/docs/concepts/ca-injector.md" + } + ] + }, + { + "title": "Reference", + "routes": [ + { + "title": "Introduction", + "path": "/docs/reference/README.md" + }, + { + "title": "Command Line Tool (cmctl)", + "path": "/docs/reference/cmctl.md" + }, + { + "title": "TLS Terminology", + "path": "/docs/reference/tls-terminology.md" + }, + + { + "title": "Components / Docker Images", + "routes": [ + { + "title": "Introduction", + "path": "/docs/cli/README.md" + }, + { + "title": "acmesolver", + "path": "/docs/cli/acmesolver.md" + }, + { + "title": "cainjector", + "path": "/docs/cli/cainjector.md" + }, + { + "title": "cmctl", + "path": "/docs/cli/cmctl.md" + }, + { + "title": "controller", + "path": "/docs/cli/controller.md" + }, + { + "title": "webhook", + "path": "/docs/cli/webhook.md" + } + ] + }, + { + "title": "API Reference", + "path": "/docs/reference/api-docs.md" + } + ] + } + ] + } + ] +} diff --git a/content/v1.12-docs/projects/README.md b/content/v1.12-docs/projects/README.md new file mode 100644 index 0000000000..185df2fce4 --- /dev/null +++ b/content/v1.12-docs/projects/README.md @@ -0,0 +1,31 @@ +--- +title: Projects +description: 'Satellite Projects of cert-manager' +--- + +The cert-manager project has a number of [satellite projects](https://github.com/cert-manager) +that extend the project's functionality, and complement the core cert-manager feature-set. + +These tools help with security, compliance and control. + +- [istio-csr](./istio-csr.md): Secure Istio service mesh with istio-csr which is + an agent that allows for [Istio](https://istio.io) workload and control plane + components to be secured using cert-manager. +- [approver-policy](./approver-policy/README.md): + a cert-manager **approver** that will automatically approve or deny + certificate requests based on defined policy. +- [csi-driver](./csi-driver.md): + a Container Storage Interface (CSI) driver plugin for Kubernetes to work along + cert-manager. The goal for this plugin is to seamlessly request and mount + certificate key pairs to pods. This is useful for facilitating mTLS, or + otherwise securing connections of pods with guaranteed present certificates + whilst having all of the features that cert-manager provides. +- [csi-driver-spiffe](./csi-driver-spiffe.md): + another CSI driver plugin to work along cert-manager. This CSI driver + transparently delivers [SPIFFE](https://spiffe.io/) + [SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-verifiable-identity-document-svid) + in the form of X.509 certificate key pairs to mounting Kubernetes Pods. The + end result is all and any Pod running in Kubernetes can securely request their + SPIFFE identity document from a Trust Domain with minimal configuration. +- [trust-manager](./trust-manager/README.md): the easiest way to manage TLS trust bundles in Kubernetes and OpenShift clusters. +- [trust-manager API reference](./trust-manager/api-reference.md): full documentation of the trust-manager CRD(s) diff --git a/content/v1.12-docs/projects/approver-policy/README.md b/content/v1.12-docs/projects/approver-policy/README.md new file mode 100644 index 0000000000..aa7efcef6c --- /dev/null +++ b/content/v1.12-docs/projects/approver-policy/README.md @@ -0,0 +1,422 @@ +--- +title: approver-policy +description: 'Policy plugin for cert-manager' +--- + +approver-policy is a cert-manager +[approver](../../concepts/certificaterequest.md#approval) +that will approve or deny CertificateRequests based on policies defined in +the `CertificateRequestPolicy` custom resource. + +## Prerequisites + +[cert-manager must be installed](../../installation/README.md), and +the [the default approver in cert-manager must be disabled](../../concepts/certificaterequest.md#approver-controller). + +> ⚠️ If the default approver is not disabled in cert-manager, approver-policy will +> race with cert-manager and policy will be ineffective. + +If you install cert-manager using `helm install` or `helm upgrade`, +you can disable the default approver by [Customizing the Chart Before Installing](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing) using the `--set` or `--values` command line flags: + +``` +# Example --set value +--set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver +``` + +```yaml +# Example --values file content +extraArgs: + - "--controllers=*,-certificaterequests-approver" # ⚠ Disable cert-manager's built-in approver +``` + +Here's a full example which will install cert-manager or reconfigure it if it is already installed: + +```terminal +helm upgrade cert-manager jetstack/cert-manager \ + --install \ + --create-namespace \ + --namespace cert-manager \ + --version REPLACE-WITH-YOUR-CERT-MANAGER-VERSION \ + --set installCRDs=true \ + --set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver +``` + +> ℹ️ The `--set installCRDs=true` setting is a convenient way to install the +> cert-manager CRDS, but it is optional and has some drawbacks. +> Read [Helm: Installing Custom Resource Definitions](https://deploy-preview-1216--cert-manager-website.netlify.app/docs/installation/helm/#3-install-customresourcedefinitions) to learn more. +> +> ℹ️ Be sure to customize the cert-manager controller `extraArgs`, +> which are at the top level of the values file. +> *Do not* change the `webhook.extraArgs`, `startupAPICheck.extraArgs` or `cainjector.extraArgs` settings. +> +> ⚠️ If you are reconfiguring an already installed cert-manager, +> check whether the original installation already customized the `extraArgs` value +> by running `helm get values cert-manager --namespace cert-manager`. +> If there are already `extraArgs` values, merge those with the extra `--controllers` value. +> Otherwise your original `extraArgs` values will be overwritten. + +## Installation + +To install approver-policy: + +```terminal +helm repo add jetstack https://charts.jetstack.io --force-update +helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait +``` + +If you are using approver-policy with [external +issuers](../../configuration/external.md), you _must_ +include their signer names so that approver-policy has permissions to approve +and deny CertificateRequests that +[reference them](../../concepts/certificaterequest.md#rbac-syntax). +For example, if using approver-policy for the internal issuer types, along with +[google-cas-issuer](https://github.com/jetstack/google-cas-issuer), and +[aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer), +set the following values when installing: + +```terminal +$ helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait \ + --set app.approveSignerNames="{\ +issuers.cert-manager.io/*,clusterissuers.cert-manager.io/*,\ +googlecasclusterissuers.cas-issuer.jetstack.io/*,googlecasissuers.cas-issuer.jetstack.io/*,\ +awspcaclusterissuers.awspca.cert-manager.io/*,awspcaissuers.awspca.cert-manager.io/*\ +}" +``` + +## Configuration + +> Example policy resources can be found +> [here](https://github.com/cert-manager/approver-policy/tree/main/docs/examples). + +When a CertificateRequest is created, approver-policy will evaluate whether the +request is appropriate for any existing policy, and if so, evaluate whether it +should be approved or denied. + +For a CertificateRequest to be appropriate for a policy and therefore be +evaluated by it, it must be both bound via RBAC _and_ be selected by the policy +selector. CertificateRequestPolicy currently only supports `issuerRef` as a +selector. + +**If at least one policy permits the request, the request is approved. If at +least one policy is appropriate for the request but none of those permit the +request, the request is denied.** + +A denied CertificateRequest is considered to be permanently failed. If it was +created for a Certificate resource, the issuance will be retried with +[exponential backoff](../../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) +like all other permanent issuance failures. A CertificateRequest that is neither +approved nor denied (because no matching policy was found) will not be further +processed by cert-manager until it gets either approved or denied. + +CertificateRequestPolicies are cluster scoped resources that can be thought of +as "policy profiles". They describe any request that is approved by that +policy. Policies are bound to Kubernetes users and ServiceAccounts using RBAC. + +Below is an example of a policy that is bound to all Kubernetes users who may +only request certificates that have the common name of `"hello.world"`. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: test-policy +spec: + allowed: + commonName: + value: "hello.world" + required: true + selector: + # Select all IssuerRef + issuerRef: {} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-policy:hello-world +rules: + - apiGroups: ["policy.cert-manager.io"] + resources: ["certificaterequestpolicies"] + verbs: ["use"] + # Name of the CertificateRequestPolicies to be used. + resourceNames: ["test-policy"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-policy:hello-world +roleRef: +# ClusterRole or Role _must_ be bound to a user for the policy to be considered. + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-policy:hello-world +subjects: +# The users who should be bound to the policies defined. +# Note that in the case of users creating Certificate resources, cert-manager +# is the entity that is creating the actual CertificateRequests, and so the +# cert-manager controller's +# Service Account should be bound instead. +- kind: Group + name: system:authenticated + apiGroup: rbac.authorization.k8s.io +``` + +## Behavior + +CertificateRequestPolicy are split into 4 parts; `allowed`, `contraints`, +`selector`, and `plugins`. + +### Allowed + +Allowed is the block that defines attributes that match against the +corresponding attribute in the request. A request is permitted by the policy if +the request omits an allowed attribute, but will _deny_ the request if it +contains an attribute which is _not_ present in the allowed block. + +An allowed attribute can be marked as `required`, which if true, will enforce +that the attribute has been defined in the request. A field can only be marked +as `required` if the corresponding field is also defined. The `required` field +is not available for `isCA` or `usages`. + +In the following CertificateRequestPolicy, a request will be permitted if it +does not request a DNS name, requests the DNS name `"example.com"`, but will be +denied when requesting `"bar.example.com"`. + +```yaml +spec: + ... + allowed: + dnsNames: + values: + - "example.com" + - "foo.example.com" + ... +``` + +In the following, a request will be denied if the request contains no Common +Name, but will permit requests whose Common Name ends in ".com". + +```yaml +spec: + ... + allowed: + commonName: + value: "*.com" + required: true + ... +``` + +If an allowed field is omitted, that attribute is considered "deny all" for +requests. + +Allowed string fields accept wildcards "\*" within its values. Wildcards "\*" in +patterns represent any string that has a length of 0 or more. A pattern +containing only "\*" will match anything. A pattern containing `"\*foo"` will +match `"foo"` as well as any string which ends in `"foo"` (e.g. `"bar-foo"`). A +pattern containing `"\*.foo"` will match `"bar-123.foo"`, but not `"barfoo"`. + +Allowed fields that are lists will permit requests that are a subset of that +list. This means that if `usages` contains `["server auth", "client auth"]`, +then a request containing only `["server auth"]` would be permitted, but not +`["server auth", "cert sign"]`. + +Below is an example including all supported allowed fields of +CertificateRequestPolicy. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: my-policy +spec: + allowed: + commonName: + value: "example.com" + dnsNames: + values: + - "example.com" + - "*.example.com" + ipAddresses: + values: + - "1.2.3.4" + - "10.0.1.*" + uris: + values: + - "spiffe://example.org/ns/*/sa/*" + emailAddresses: + values: + - "*@example.com" + required: true + isCA: false + usages: + - "server auth" + - "client auth" + subject: + organizations: + values: ["hello-world"] + countries: + values: ["*"] + organizationalUnits: + values: ["*"] + localities: + values: ["*"] + provinces: + values: ["*"] + streetAddresses: + values: ["*"] + postalCodes: + values: ["*"] + serialNumber: + value: "*" + ... +``` + +### Constraints + +Constraints is the block that is used to limit what attributes the request can +have. If a constraint is not defined, then the attribute is considered "allow +all". + +Below is an example containing all supported constraints fields of +CertificateRequestPolicy. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: my-policy +spec: + ... + constraints: + minDuration: 1h + maxDuration: 24h + privateKey: + algorithm: RSA + minSize: 2048 + maxSize: 4096 + ... +``` + +### Selector + +Selector is a required field that is used for matching +CertificateRequestPolicies against CertificateRequests for evaluation. A +CertificateRequestPolicy must select, and therefore match, a CertificateRequest +for it to be considered for evaluation of the request. + +> ⚠️ Note that the user must still be bound by [RBAC](#configuration) for +> the policy to be considered for evaluation against a request. + +approver-policy supports selecting over the `issuerRef` and the `namespace` of a +request. + +At least either an `issuerRef` *or* `namespace` selector must be defined, even +if set to empty (`{}`). **Both** selectors must match on a CertificateRequest +for the request to evaluated by the policy if both are defined. + +#### `issuerRef` + +The `issuerRef` CertificateRequestPolicy selector selects on the corresponding +`issuerRef` stanza on the CertificateRequest. + +`issuerRef` values accept wildcards "\*". If an `issuerRef` is set to an empty +object `{}`, then the policy will match against _all_ requests. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: my-policy +spec: + ... + selector: + issuerRef: + name: "my-ca" + kind: "*Issuer" + group: "cert-manager.io" +``` + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: match-all-requests +spec: + ... + selector: + issuerRef: {} +``` + +#### `namespace` + +The `namespace` CertificateRequestPolicy selector selects on the Namespace to +which the CertificateRequest was created in. The selector can be defined with +either `matchNames` or `matchLabels`. + +`matchNames` takes a list of strings which match the _name_ of the Namespace. +Accepts wildcards "\*". + +`matchLabels` takes a list of key value strings which match on the labels of the +Namespace that the CertificateRequest was created in. Please see the [Kubernetes +documentation][] for more information on `matchLabels` behavior. + +If a `namespace` is set to an empty object `{}`, then the policy will match +against _all_ requests. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: my-policy +spec: + ... + selector: + namespace: + matchNames: + - "default" + - "app-team-*" + matchLabels: + foo: bar + team: dev +``` + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: match-all-requests +spec: + ... + selector: + namespace: {} +``` + +[Kubernetes documentation]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#resources-that-support-set-based-requirements + +### Plugins + +Plugins are external approvers that are built into approver-policy at compile +time. Plugins are designed to be used as extensions to the existing policy +checks where the user requires special functionality that the existing checks +can't provide. + +Plugins are defined as a block on the CertificateRequestPolicy `spec`. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: plugins +spec: + ... + plugins: + my-plugin: + values: + val-1: key-1 +``` + +There are currently no open source plugins. + +## API Reference + +> 📖 Read the [approver-policy API reference](api-reference.md). diff --git a/content/v1.12-docs/projects/approver-policy/api-reference.md b/content/v1.12-docs/projects/approver-policy/api-reference.md new file mode 100644 index 0000000000..e4d45bb829 --- /dev/null +++ b/content/v1.12-docs/projects/approver-policy/api-reference.md @@ -0,0 +1,978 @@ +--- +title: approver-policy API Reference +description: "approver-policy API documentation" +--- + +Packages: + +- [`policy.cert-manager.io/v1alpha1`](#policycert-manageriov1alpha1) + +# `policy.cert-manager.io/v1alpha1` + +Resource Types: + + +- [CertificateRequestPolicy](#certificaterequestpolicy) + + + + +## `CertificateRequestPolicy` + + + + + +CertificateRequestPolicy is an object for describing a "policy profile" that makes decisions on whether applicable CertificateRequests should be approved or denied. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringpolicy.cert-manager.io/v1alpha1true
kindstringCertificateRequestPolicytrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy.
+
false
statusobject + CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy.
+
false
+ + +### `CertificateRequestPolicy.spec` + + +CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
selectorobject + Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation.
+
true
allowedobject + Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible.
+
false
constraintsobject + Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute.
+
false
pluginsmap[string]object + Plugins define a set of plugins and their configuration that should be executed when this policy is evaluated against a CertificateRequest. A plugin must already be built within approver-policy for it to be available.
+
false
+ + +### `CertificateRequestPolicy.spec.selector` + + +Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
issuerRefobject + IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". + The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ```
+
false
namespaceobject + Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected.
+
false
+ + +### `CertificateRequestPolicy.spec.selector.issuerRef` + + +IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". + The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ``` + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
groupstring + Group is the wildcard selector to match the `spec.issuerRef.group` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
+
false
kindstring + Kind is the wildcard selector to match the `spec.issuerRef.kind` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
+
false
namestring + Name is the wildcard selector to match the `spec.issuerRef.name` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
+
false
+ + +### `CertificateRequestPolicy.spec.selector.namespace` + + +Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchLabelsmap[string]string + MatchLabels is the set of Namespace labels that select on CertificateRequests which have been created in a Namespace matching the selector.
+
false
matchNames[]string + MatchNames are the set of Namespace names that select on CertificateRequests that have been created in a matching Namespace. Accepts wildcards "*".
+
false
+ + +### `CertificateRequestPolicy.spec.allowed` + + +Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
commonNameobject + CommonName defines the X.509 Common Name that is permissible.
+
false
dnsNamesobject + DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*".
+
false
emailAddressesobject + EmailAddresses defines the X.509 Email SANs that may be requested for.
+
false
ipAddressesobject + IPAddresses defines the X.509 IP SANs that may be requested for.
+
false
isCAboolean + IsCA defines whether it is permissible for a CertificateRequest to have the `spec.IsCA` field set to `true`. An omitted field, value of `nil` or `false`, forbids the `spec.IsCA` field from bring `true`. A value of `true` permits CertificateRequests setting the `spec.IsCA` field to `true`.
+
false
subjectobject + Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested.
+
false
urisobject + URIs defines the X.509 URI SANs that may be requested for.
+
false
usages[]enum + Usages defines the list of permissible key usages that may appear on the CertificateRequest `spec.keyUsages` field. An omitted field or value of `nil` forbids any Usages being requested. An empty slice `[]` is equivalent to `nil`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.commonName` + + +CommonName defines the X.509 Common Name that is permissible. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
+
false
valuestring + Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.dnsNames` + + +DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*". + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.emailAddresses` + + +EmailAddresses defines the X.509 Email SANs that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.ipAddresses` + + +IPAddresses defines the X.509 IP SANs that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject` + + +Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
countriesobject + Countries define the X.509 Subject Countries that may be requested for.
+
false
localitiesobject + Localities defines the X.509 Subject Localities that may be requested for.
+
false
organizationalUnitsobject + OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for.
+
false
organizationsobject + Organizations define the X.509 Subject Organizations that may be requested for.
+
false
postalCodesobject + PostalCodes defines the X.509 Subject Postal Codes that may be requested for.
+
false
provincesobject + Provinces defines the X.509 Subject Provinces that may be requested for.
+
false
serialNumberobject + SerialNumber defines the X.509 Subject Serial Number that may be requested for.
+
false
streetAddressesobject + StreetAddresses defines the X.509 Subject Street Addresses that may be requested for.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.countries` + + +Countries define the X.509 Subject Countries that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.localities` + + +Localities defines the X.509 Subject Localities that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.organizationalUnits` + + +OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.organizations` + + +Organizations define the X.509 Subject Organizations that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.postalCodes` + + +PostalCodes defines the X.509 Subject Postal Codes that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.provinces` + + +Provinces defines the X.509 Subject Provinces that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.serialNumber` + + +SerialNumber defines the X.509 Subject Serial Number that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
+
false
valuestring + Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.subject.streetAddresses` + + +StreetAddresses defines the X.509 Subject Street Addresses that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.allowed.uris` + + +URIs defines the X.509 URI SANs that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
+
false
values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
+
false
+ + +### `CertificateRequestPolicy.spec.constraints` + + +Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
maxDurationstring + MaxDuration defines the maximum duration a certificate may be requested for. Values are inclusive (i.e. a max value of `1h` will accept a duration of `1h`). MaxDuration and MinDuration may be the same value. An omitted field or value of `nil` permits any maximum duration. If MaxDuration is defined, a duration _must_ be requested on the CertificateRequest.
+
false
minDurationstring + MinDuration defines the minimum duration a certificate may be requested for. Values are inclusive (i.e. a min value of `1h` will accept a duration of `1h`). MinDuration and MaxDuration may be the same value. An omitted field or value of `nil` permits any minimum duration. If MinDuration is defined, a duration _must_ be requested on the CertificateRequest.
+
false
privateKeyobject + PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor.
+
false
+ + +### `CertificateRequestPolicy.spec.constraints.privateKey` + + +PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
algorithmenum + Algorithm defines the allowed crypto algorithm that is used by the requestor for their private key in their request. An omitted field or value of `nil` permits any Algorithm.
+
+ Enum: RSA, ECDSA, Ed25519
+
false
maxSizeinteger + MaxSize defines the maximum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MaxSize and MinSize may be the same value. An omitted field or value of `nil` permits any maximum size.
+
false
minSizeinteger + MinSize defines the minimum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MinSize and MaxSize may be the same value. An omitted field or value of `nil` permits any minimum size.
+
false
+ + +### `CertificateRequestPolicy.spec.plugins[key]` + + +CertificateRequestPolicyPluginData is configuration needed by the plugin approver to evaluate a CertificateRequest on this policy. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
valuesmap[string]string + Values define a set of well-known, to the plugin, key value pairs that are required for the plugin to successfully evaluate a request based on this policy.
+
false
+ + +### `CertificateRequestPolicy.status` + + +CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
conditions[]object + List of status conditions to indicate the status of the CertificateRequestPolicy. Known condition types are `Ready`.
+
false
+ + +### `CertificateRequestPolicy.status.conditions[index]` + + +CertificateRequestPolicyCondition contains condition information for a CertificateRequestPolicyStatus. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
statusstring + Status of the condition, one of ('True', 'False', 'Unknown').
+
true
typestring + Type of the condition, known values are (`Ready`).
+
true
lastTransitionTimestring + LastTransitionTime is the timestamp corresponding to the last status change of this condition.
+
+ Format: date-time
+
false
messagestring + Message is a human readable description of the details of the last transition, complementing reason.
+
false
observedGenerationinteger + If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the CertificateRequestPolicy.
+
+ Format: int64
+
false
reasonstring + Reason is a brief machine readable explanation for the condition's last transition.
+
false
diff --git a/content/v1.12-docs/projects/csi-driver-spiffe.md b/content/v1.12-docs/projects/csi-driver-spiffe.md new file mode 100644 index 0000000000..771ce6a1ae --- /dev/null +++ b/content/v1.12-docs/projects/csi-driver-spiffe.md @@ -0,0 +1,257 @@ +--- +title: csi-driver-spiffe +description: 'Container Storage Interface (CSI) driver plugin for Kubernetes, providing SPIFFE SVIDs using cert-manager' +--- + +csi-driver-spiffe is a Container Storage Interface (CSI) driver plugin for +Kubernetes, designed to work alongside [cert-manager](https://cert-manager.io/). + +It transparently delivers [SPIFFE](https://spiffe.io/) [SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-verifiable-identity-document-svid) +(in the form of X.509 certificate key pairs) to mounting Kubernetes Pods. + +The end result is that any and all Pods running in Kubernetes can securely request +a SPIFFE identity document from a Trust Domain with minimal configuration. + +These documents in turn have the following properties: + +- automatically renewed ✔️ +- private key never leaves the node's virtual memory ✔️ +- each Pod's document is unique ✔️ +- the document shares the same life cycle as the Pod and is destroyed on Pod termination ✔️ + +```yaml +... + volumeMounts: + - mountPath: "/var/run/secrets/spiffe.io" + name: spiffe + volumes: + - name: spiffe + csi: + driver: spiffe.csi.cert-manager.io + readOnly: true +``` + +SPIFFE documents can then be used by Pods for mutual TLS (mTLS) or other authentication within their Trust Domain. +### Components + +The project is split into two components. + +#### CSI Driver + +The CSI driver runs as DaemonSet on the cluster which is responsible for +generating, requesting, and mounting the certificate key pair to Pods on the +node it manages. The CSI driver creates and manages a +[tmpfs](https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html) directory +which is used to create and mount Pod volumes from. + +When a Pod is created with the CSI volume configured, the +driver will locally generate a private key, and create a cert-manager +[CertificateRequest](../concepts/certificaterequest.md) +in the same Namespace as the Pod. + +The driver uses [CSI Token Request](https://kubernetes-csi.github.io/docs/token-requests.html) to both +discover the Pod's identity to form the SPIFFE identity contained in the X.509 +certificate signing request, as well as securely impersonate its ServiceAccount +when creating the CertificateRequest. + +Once signed by the pre-configured target signer, the driver will mount the +private key and signed certificate into the Pod's Volume to be made available as +a Volume Mount. This certificate key pair is regularly renewed based on the +expiry of the signed certificate. + +#### Approver + +A distinct [cert-manager approver](../concepts/certificaterequest.md#approval) +Deployment is responsible for managing the approval and denial condition of +created CertificateRequests that target the configured SPIFFE Trust Domain +signer. + +The approver ensures that requests have: + +1. acceptable key usages (Key Encipherment, Digital Signature, Client Auth, Server Auth); +2. a requested duration which matches the enforced duration (default 1 hour); +3. no [SANs](https://en.wikipedia.org/wiki/Subject_Alternative_Name) or other + identifiable attributes except a single [URI SAN](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier); +4. a URI SAN which is the SPIFFE identity of the ServiceAccount which created + the CertificateRequest; +5. a SPIFFE ID Trust Domain matching the one that was configured at startup. + +If any of these checks do not pass, the CertificateRequest will be marked as +Denied, else it will be marked as Approved. The approver will only manage +CertificateRequests who request from the same [IssuerRef](../concepts/certificaterequest.md) +that has been configured. + +## Installation + +### Requirements + +csi-driver-spiffe generally requires Kubernetes version `v1.21` or newer. + +If running on Kubernetes `v1.20`, you'll need the `--feature-gates=CSIServiceAccountToken=true` flag. + +cert-manager `v1.3` or higher is also required. + +### Steps + +#### 1. Install cert-manager + +csi-driver-spiffe requires cert-manager to be [installed](../installation/README.md) but +a default installation of cert-manager **will not work**. + +> ⚠️ It is **vital** that the [default approver is disabled in cert-manager](../concepts/certificaterequest.md#approver-controller) ⚠️ + +If the default approver is not disabled, the csi-driver-spiffe approver will +race with cert-manager and policy enforcement will become useless. + +```bash +helm repo add jetstack https://charts.jetstack.io --force-update + +# NOTE: This isn't the usual cert-manager install process; +# we're disabling the cert-manager approver. +# See explanation above! + +helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager \ + --set extraArgs={--controllers='*\,-certificaterequests-approver'} \ + --set installCRDs=true \ + --create-namespace +``` + +#### 2. Configure an Issuer / ClusterIssuer + +Install or configure a [ClusterIssuer](../configuration/README.md) to give +cert-manager the ability to sign against your Trust Domain. + +If you want a namespace-scoped Issuer, then it must be created in every namespace +that Pods will mount volumes from. + +You must use an Issuer type which is compatible with signing URI SAN certificates; +ACME issuers won't generally work, and the SelfSigned issuer is not appropriate. + +An example demo [ClusterIssuer](../concepts/issuer.md#namespaces) can +be found [in the csi-driver-spiffe repo](https://github.com/cert-manager/csi-driver-spiffe/blob/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/clusterissuer.yaml). + +> ⚠️ This Trust Domain's root CA is generated by cert-manager and **the private key is stored in the cluster** +> This might not be appropriate for production deployments! + +We'll also use [cmctl](../reference/cmctl.md) to approve the CertificateRequest, +since the default approver was disabled above. + +```terminal +kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/clusterissuer.yaml + +# We must also approve the CertificateRequest since we +# disabled the default approver +cmctl approve -n cert-manager \ + $(kubectl get cr -n cert-manager -ojsonpath='{.items[0].metadata.name}') +``` + +#### 3. Install csi-driver-spiffe + +Install csi-driver-spiffe into the cluster using the issuer we configured. We +must also configure the issuer resource type and name of the issuer we +configured so that the approver has [permissions to approve referencing CertificateRequests](../concepts/certificaterequest.md#rbac-syntax). + +Note that the `issuer.name`, `issuer.kind` and `issuer.group` will need to be changed to match +the issuer you're actually using! + +```bash +helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \ + --set "app.logLevel=1" \ + --set "app.trustDomain=my.trust.domain" \ + --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \ + \ + --set "app.issuer.name=csi-driver-spiffe-ca" \ + --set "app.issuer.kind=ClusterIssuer" \ + --set "app.issuer.group=cert-manager.io" +``` + +## Usage + +Once the driver is successfully installed, Pods can begin to request and mount +their key and SPIFFE certificate. Since the Pod's ServiceAccount is impersonated +when creating CertificateRequests, every ServiceAccount must be given that +permission which intends to use the volume. + +Example manifest with a dummy Deployment: + +```bash +kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/example-app.yaml + +kubectl exec -n sandbox \ + $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') \ + -- \ + cat /var/run/secrets/spiffe.io/tls.crt | \ + openssl x509 --noout --text | \ + grep "Issuer:" +# expected output: Issuer: CN = csi-driver-spiffe-ca + +kubectl exec -n sandbox \ + $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') \ + -- \ + cat /var/run/secrets/spiffe.io/tls.crt | \ + openssl x509 --noout --text | \ + grep "URI:" +# expected output: URI:spiffe://foo.bar/ns/sandbox/sa/example-app +``` + +### FS-Group + +When running Pods with a specified user or group, the volume will not be +readable by default due to Unix based file system permissions. The mounting +volumes file group can be specified using the following volume attribute: + +```yaml +... + securityContext: + runAsUser: 123 + runAsGroup: 456 + volumes: + - name: spiffe + csi: + driver: spiffe.csi.cert-manager.io + readOnly: true + volumeAttributes: + spiffe.csi.cert-manager.io/fs-group: "456" +``` + +```bash +kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/fs-group-app.yaml + +kubectl exec -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app-fs-group -o jsonpath='{.items[0].metadata.name}') -- cat /var/run/secrets/spiffe.io/tls.crt | openssl x509 --noout --text | grep URI: +# expected output: URI:spiffe://foo.bar/ns/sandbox/sa/fs-group-app +``` + +### Root CA Bundle + +By default, the CSI driver will only mount the Pod's private key and signed +certificate. csi-driver-spiffe can be optionally configured to also mount a +statically defined CA bundle from a volume that will be written to all Pod +volumes. + +If the CSI driver detects this bundle has changed (through overwrite, renewal, +etc), the new bundle will be written to all existing volumes. + +The following example mounts the CA certificate used by the Trust Domain +ClusterIssuer. + +```terminal +helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \ + --set "app.logLevel=1" \ + --set "app.trustDomain=my.trust.domain" \ + --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \ + \ + --set "app.issuer.name=csi-driver-spiffe-ca" \ + --set "app.issuer.kind=ClusterIssuer" \ + --set "app.issuer.group=cert-manager.io" \ + \ + --set "app.driver.volumes[0].name=root-cas" \ + --set "app.driver.volumes[0].secret.secretName=csi-driver-spiffe-ca" \ + --set "app.driver.volumeMounts[0].name=root-cas" \ + --set "app.driver.volumeMounts[0].mountPath=/var/run/secrets/cert-manager-csi-driver-spiffe" \ + --set "app.driver.sourceCABundle=/var/run/secrets/cert-manager-csi-driver-spiffe/ca.crt" + +kubectl rollout restart deployment -n sandbox my-csi-app + +kubectl exec -it -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') -- ls /var/run/secrets/spiffe.io/ +# expected output: ca.crt tls.crt tls.key +``` diff --git a/content/v1.12-docs/projects/csi-driver.md b/content/v1.12-docs/projects/csi-driver.md new file mode 100644 index 0000000000..31f7f1c74d --- /dev/null +++ b/content/v1.12-docs/projects/csi-driver.md @@ -0,0 +1,231 @@ +--- +title: csi-driver +description: '' +--- + +csi-driver is a Container Storage Interface (CSI) driver plugin for Kubernetes +to work along cert-manager. The goal for this plugin is to seamlessly request +and mount certificate key pairs to pods. This is useful for facilitating mTLS, +or otherwise securing connections of pods with guaranteed present certificates +whilst having all of the features that cert-manager provides. + +## Why a CSI Driver? + +- Ensure private keys never leave the node and are never sent over the network. + All private keys are stored locally on the node. +- Unique key and certificate per application replica with a grantee to be + present on application run time. +- Reduce resource management overhead by defining certificate request spec + in-line of the Kubernetes Pod template. +- Automatic renewal of certificates based on expiry of each individual + certificate. +- Keys and certificates are destroyed during application termination. +- Scope for extending plugin behavior with visibility on each replica's + certificate request and termination. + +## Requirements and Installation + +This CSI driver plugin makes use of the 'CSI inline volume' feature - Alpha as +of `v1.15` and beta in `v1.16`. Kubernetes versions `v1.16` and higher require +no extra configuration however `v1.15` requires the following feature gate set: +``` +--feature-gates=CSIInlineVolume=true +``` + +You must have a working installation of cert-manager present on the cluster. +Instructions on how to install cert-manager can be found +[on cert-manager.io](../installation/README.md). + +To install the csi-driver, use helm install: + +```terminal +helm repo add jetstack https://charts.jetstack.io --force-update +helm upgrade -i -n cert-manager cert-manager-csi-driver jetstack/cert-manager-csi-driver --wait +``` + +Or apply the static manifests to your cluster: + +```terminal +helm repo add jetstack https://charts.jetstack.io --force-update +helm template jetstack/cert-manager-csi-driver | kubectl apply -n cert-manager -f - +``` + + +You can verify the installation has completed correctly by checking the presence +of the CSIDriver resource as well as a CSINode resource present for each node, +referencing `csi.cert-manager.io`. + +``` +$ kubectl get csidrivers +NAME CREATED AT +csi.cert-manager.io 2019-09-06T16:55:19Z + +$ kubectl get csinodes -o yaml +apiVersion: v1 +items: +- apiVersion: storage.k8s.io/v1beta1 + kind: CSINode + metadata: + name: kind-control-plane + ownerReferences: + - apiVersion: v1 + kind: Node + name: kind-control-plane +... + spec: + drivers: + - name: csi.cert-manager.io + nodeID: kind-control-plane + topologyKeys: null +... +``` + +The CSI driver is now installed and is ready to be used for pods in the cluster. + +## Requesting and Mounting Certificates + +To request certificates from cert-manager, simply define a volume mount where +the key and certificate will be written to, along with a volume with attributes +that define the cert-manager request. The following is a dummy app that mounts a +key certificate pair to `/tls` and has been signed by the `ca-issuer` with a DNS +name valid for `my-service.sandbox.svc.cluster.local`. + +``` +apiVersion: v1 +kind: Pod +metadata: + name: my-csi-app + namespace: sandbox + labels: + app: my-csi-app +spec: + containers: + - name: my-frontend + image: busybox + volumeMounts: + - mountPath: "/tls" + name: tls + command: [ "sleep", "1000000" ] + volumes: + - name: tls + csi: + driver: csi.cert-manager.io + volumeAttributes: + csi.cert-manager.io/issuer-name: ca-issuer + csi.cert-manager.io/dns-names: ${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local +``` + +Once created, the CSI driver will generate a private key locally, request a +certificate from cert-manager based on the given attributes, then store both +locally to be mounted to the pod. The pod will remain in a pending state until +this process has been completed. + +For more information on how to set up issuers for your cluster, refer to the +cert-manager documentation +[here](../configuration/README.md). **Note** it is not +possible to use `SelfSigned` Issuers with the CSI Driver. In order for +cert-manager to self sign a certificate, it needs access to the secret +containing the private key that signed the certificate request to sign the end +certificate. This secret is not used and so not available in the CSI driver use +case. + +## Supported Volume Attributes + +The csi-driver driver aims to have complete feature parity with all possible +values available through the cert-manager API however currently supports the +following values; + +| Attribute | Description | Default | Example | +|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|----------------------------------| +| `csi.cert-manager.io/issuer-name` | The Issuer name to sign the certificate request. | | `ca-issuer` | +| `csi.cert-manager.io/issuer-kind` | The Issuer kind to sign the certificate request. | `Issuer` | `ClusterIssuer` | +| `csi.cert-manager.io/issuer-group` | The group name the Issuer belongs to. | `cert-manager.io` | `out.of.tree.foo` | +| `csi.cert-manager.io/common-name` | Certificate common name (supports variables). | | `my-cert.foo` | +| `csi.cert-manager.io/dns-names` | DNS names the certificate will be requested for. At least a DNS Name, IP or URI name must be present (supports variables). | | `a.b.foo.com,c.d.foo.com` | +| `csi.cert-manager.io/ip-sans` | IP addresses the certificate will be requested for. | | `192.0.0.1,192.0.0.2` | +| `csi.cert-manager.io/uri-sans` | URI names the certificate will be requested for (supports variables). | | `spiffe://foo.bar.cluster.local` | +| `csi.cert-manager.io/duration` | Requested duration the signed certificate will be valid for. | `720h` | `1880h` | +| `csi.cert-manager.io/is-ca` | Mark the certificate as a certificate authority. | `false` | `true` | +| `csi.cert-manager.io/key-usages` | Set the key usages on the certificate request. | `digital signature,key encipherment` | `server auth,client auth` | +| `csi.cert-manager.io/key-encoding` | Set the key encoding format (PKCS1 or PKCS8). | `PKCS1` | `PKCS8` | +| `csi.cert-manager.io/certificate-file` | File name to store the certificate file at. | `tls.crt` | `foo.crt` | +| `csi.cert-manager.io/ca-file` | File name to store the ca certificate file at. | `ca.crt` | `foo.ca` | +| `csi.cert-manager.io/privatekey-file` | File name to store the key file at. | `tls.key` | `foo.key` | +| `csi.cert-manager.io/fs-group` | Set the FS Group of written files. Should be paired with and match the value of the consuming container `runAsGroup`. | | `2000` | +| `csi.cert-manager.io/renew-before` | The time to renew the certificate before expiry. Defaults to a third of the requested duration. | `$CERT_DURATION/3` | `72h` | +| `csi.cert-manager.io/reuse-private-key` | Re-use the same private when when renewing certificates. | `false` | `true` | +| `csi.cert-manager.io/pkcs12-enable` | Enable writing the signed certificate chain and private key as a PKCS12 file. | | `true` | +| `csi.cert-manager.io/pkcs12-filename` | File location to write the PKCS12 file. Requires `csi.cert-manager.io/keystore-pkcs12-enable` be set to `true`. | `keystore.p12` | `tls.p12` | +| `csi.cert-manager.io/pkcs12-password` | Password used to encode the PKCS12 file. Required when PKCS12 is enabled (`csi.cert-manager.io/keystore-pkcs12-enable: true`). | | `my-password` | + +### Variables + +The following attributes support variables that are evaluated when a request is +made for the mounting Pod. These variables are useful for constructing requests +with SANs that contain values from the mounting Pod. + +``` +`csi.cert-manager.io/common-name` +`csi.cert-manager.io/dns-names` +`csi.cert-manager.io/uri-sans` +``` + +Variables follow the [go `os.Expand`](https://pkg.go.dev/os#Expand) structure, +which is generally what you would expect on a UNIX shell. The CSI driver has +access to the following variables: + +``` +${POD_NAME} +${POD_NAMESPACE} +${POD_UID} +${SERVICE_ACCOUNT_NAME} +``` + +#### Example Usage + +```yaml +volumeAttributes: + csi.cert-manager.io/issuer-name: ca-issuer + csi.cert-manager.io/dns-names: "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local" + csi.cert-manager.io/uri-sans: "spiffe://cluster.local/ns/${POD_NAMESPACE}/pod/${POD_NAME}/${POD_UID}" + csi.cert-manager.io/common-name: "${SERVICE_ACCOUNT_NAME}.${POD_NAMESPACE}" +``` + +## Requesting Certificates using the mounting Pod's ServiceAccount + +If the flag `--use-token-request` is enabled on the csi-driver DaemonSet, the +[CertificateRequest](../concepts/certificaterequest.md) resource will be created +by the mounting Pod's ServiceAccount. This can be pared with +[approver-policy](./approver-policy/README.md) to enable advanced policy on a per +ServiceAccount basis. + +Ensure to give permissions to Pod ServiceAccounts to create CertificateRequests +with this flag enabled, i.e: + +```yaml +# WARNING: This RBAC will enable any identiy in the cluster to create +# CertificateRequests. This may or may not be problimatic based on your security +# model. It is likely worth scoping the set of identities in the +# `ClusterRoleBinding` `subjects` stanza. +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: cert-manager-csi-driver-all-cr-create +rules: +- apiGroups: ["cert-manager.io"] + resources: ["certificaterequests"] + verbs: [ "create" ] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: cert-manager-csi-driver-all-cr-create +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-csi-driver-all-cr-create +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +``` diff --git a/content/v1.12-docs/projects/istio-csr.md b/content/v1.12-docs/projects/istio-csr.md new file mode 100644 index 0000000000..57748517bc --- /dev/null +++ b/content/v1.12-docs/projects/istio-csr.md @@ -0,0 +1,92 @@ +--- +title: istio-csr +description: '' +--- + +istio-csr is an agent that allows for [Istio](https://istio.io) workload and +control plane components to be secured using +[cert-manager](https://cert-manager.io). + +Certificates facilitating mTLS — both inter +and intra-cluster — will be signed, delivered and renewed using [cert-manager +issuers](https://cert-manager.io/docs/concepts/issuer). + +## Getting Started Guide For istio-csr + +We have [a guide](../tutorials/istio-csr/istio-csr.md) for setting up istio-csr in a fresh +[kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) cluster. + +Following the guide is the best way to see istio-csr in action. + +If you've already seen istio-csr in action or if you're experienced with running +Istio and just want quick installation instructions, read on for more details. + +## Lower-Level Details (For Experienced Istio Users) + +⚠️ The [getting started](../tutorials/istio-csr/istio-csr.md) guide is a better place if you just want to try istio-csr out! + +Running istio-csr requires a few steps and preconditions in order: + +1. A cluster _without_ Istio already installed +2. cert-manager [installed](https://cert-manager.io/docs/installation/) in the cluster +3. An `Issuer` or `ClusterIssuer` which will be used to issue Istio certificates +4. istio-csr installed (likely via helm) +5. Istio [installed](https://istio.io/latest/docs/setup/install/istioctl/) with + some custom config required, e.g. using the example config from the [repository](https://github.com/cert-manager/istio-csr/tree/main/hack). + +### Why Custom Istio Install Manifests? + +If you take a look at the contents of [the example Istio install +manifests](https://github.com/cert-manager/istio-csr/tree/main/hack) +there are a few custom configuration options which are important. + +Required changes include setting `ENABLE_CA_SERVER` to `false` and setting the `caAddress` from which Istio will +request certificates; replacing the CA server is the whole point of istio-csr! + +Mounting and statically specifying the root CA is also an important recommended step. Without a manually specified +root CA istio-csr defaults to trying to discover root CAs automatically, which could theoretically lead to a +[signer hijacking attack](https://github.com/cert-manager/istio-csr/issues/103#issuecomment-923882792) if for example +a signer's token was stolen (such as the cert-manager controller's token). + +### Issuer or ClusterIssuer? + +Unless you know you need a `ClusterIssuer` we'd recommend starting with an `Issuer`, since it should be easier to reason about +the access controls for an Issuer; they're namespaced and so naturally a little more limited in scope. + +That said, if you view your entire Kubernetes cluster as being a trust domain itself, then a ClusterIssuer is the more natural +fit. The best choice will depend on your specific situation. + +Our [getting started guide](../tutorials/istio-csr/istio-csr.md) uses an `Issuer`. + +### Which Issuer Type? + +Whether you choose to use an `Issuer` or a `ClusterIssuer`, you'll also need to choose the type of issuer you want such as: + +- [CA](https://cert-manager.io/docs/configuration/ca/) +- [Vault](https://cert-manager.io/docs/configuration/vault/) +- or an [external issuer](https://cert-manager.io/docs/configuration/external/) + +The key requirement is that arbitrary values can be placed into the `subjectAltName` (SAN) X.509 extension, since +Istio places SPIFFE IDs there. + +That means that the ACME issuer **will not work** — publicly trusted certificates such as those issued by Let's Encrypt +don't allow arbitrary entries in the SAN, for very good reasons. + +If you're already using [HashiCorp Vault](https://www.vaultproject.io/) then the Vault issuer is an obvious choice. If +you want to control your own PKI entirely, we'd recommend the CA issuer. The choice is ultimately yours. + +### Installing istio-csr After Istio + +This is unsupported because it's exceptionally difficult to do safely. It's likely that installing istio-csr _after_ Istio isn't +possible to do without downtime, since installing istio-csr second would require a time period where all Istio sidecars trust +both the old Istio-managed CA and the new cert-manager controlled CA. + +## How Does istio-csr Work? + +istio-csr implements the gRPC Istio certificate service which authenticates, +authorizes, and signs incoming certificate signing requests from Istio +workloads, routing all certificate handling through cert-manager installed in +the cluster. + +This seamlessly matches the behavior of istiod in a typical installation, while +allowing certificate management through cert-manager. diff --git a/content/v1.12-docs/projects/trust-manager/README.md b/content/v1.12-docs/projects/trust-manager/README.md new file mode 100644 index 0000000000..30e388fbe1 --- /dev/null +++ b/content/v1.12-docs/projects/trust-manager/README.md @@ -0,0 +1,373 @@ +--- +title: trust-manager +description: 'Distributing Trust Bundles in Kubernetes' +--- + +trust-manager is the easiest way to manage trust bundles in Kubernetes and OpenShift clusters. + +It orchestrates bundles of trusted X.509 certificates which are primarily used for validating +certificates during a TLS handshake but can be used in other situations, too. + +## Overview + +trust-manager is a small Kubernetes operator which aims to help reduce the overhead of managing +TLS trust bundles in your clusters. + +It adds the `Bundle` custom Kubernetes resource (CRD) which can read input from various sources +and combine the resultant certificates into a bundle ready to be used by your applications. + +trust-manager ensures that it's both quick and easy to keep your trusted certificates up-to-date +and enables cluster administrators to easily automate providing a secure bundle without having +to worry about rebuilding containers to update trust stores. + +It's designed to complement cert-manager and works well when consuming CA certificates from a +cert-manager `Issuer` or `ClusterIssuer` but can be used entirely independently from cert-manager +if needed. + +## Usage + +trust-manager is intentionally simple, and adds one new Kubernetes `CustomResourceDefintion`: `Bundle`. + +A `Bundle` represents a set of PEM-encoded X.509 certificates that should be distributed and made +available across the cluster. `Bundle`s are cluster scoped. + +Users specify a list of `sources`, which trust-manager will query and concatenate certificate data from. +The only other required field is the `target`, which describes how and where the resulting bundle will +be written. + +An example `Bundle` might look like this: + +```yaml +apiVersion: trust.cert-manager.io/v1alpha1 +kind: Bundle +metadata: + name: my-org.com # The bundle name will also be used for the target +spec: + sources: + # Include a bundle of publicly trusted certificates which can be + # used to validate most TLS certificates on the internet, such as + # those issued by Let's Encrypt, Google, Amazon and others. + - useDefaultCAs: true + + # A Secret in the trust-manager namespace + - secret: + name: "my-db-tls" + key: "ca.crt" + + # A ConfigMap in the trust-manager namespace + - configMap: + name: "my-org.net" + key: "root-certs.pem" + + # A manually specified string + - inLine: | + -----BEGIN CERTIFICATE----- + MIIC5zCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl + .... + 0V3NCaQrXoh+3xrXgX/vMdijYLUSo/YPEWmo + -----END CERTIFICATE----- + target: + # Data synced to the ConfigMap `my-org.com` at the key `root-certs.pem` in + # every namespace that has the label "linkerd.io/inject=enabled". + configMap: + key: "root-certs.pem" + namespaceSelector: + matchLabels: + linkerd.io/inject: "enabled" +``` + +`Bundle` resources currently support several source types: + +- `configMap` - a `ConfigMap` resource in the trust-manager namespace +- `secret` - a `Secret` resource in the trust-manager namespace +- `inLine` - a manually specified string containing at least one certificate +- `useDefaultCAs` - usually, a bundle of publicly trusted certificates + +These sources, along with the single currently supported target type (`configMap`) +are documented in the trust-manager [API reference documentation](./api-reference.md). + +#### Namespace Selector + +A target's `namespaceSelector` is used to restrict which Namespaces your `Bundle`'s target +should be synced to. + +`namespaceSelector` supports the field `matchLabels`. + +Please see [Kubernetes documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) +for more information about how label selectors can be configured. + +If `namespaceSelector` is empty, a `Bundle`'s target will be synced to all Namespaces. + +> ⚠️ A future update to trust-manager **will** change this behavior so that an empty namespace selector will sync only +to the trust-manager namespace by default. + +## Installation + +### Helm + +Helm is the easiest way to install trust-manager and comes with a publicly trusted certificate bundle package +(for the`useDefaultCAs` source) derived from Debian containers. + +When installed via Helm, trust-manager has a dependency on cert-manager for provisioning an application certificate, +and as such trust-manager is also installed into the cert-manager namespace. + +```bash +helm repo add jetstack https://charts.jetstack.io --force-update +helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set installCRDs=true --wait --create-namespace +helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --wait +``` + +### Manual Installation + +We strongly recommend that you install trust-manager using Helm and we don't currently support manually installed +versions of trust-manager. This is so that we can focus on continuing to improve trust-manager with the resources +we currently have available. + +## Quick Start Example + +Let's get started with an example of creating our own `Bundle`! + +First we'll create a demo cluster: + +```bash +git clone https://github.com/cert-manager/trust-manager trust-manager +cd trust-manager +make demo +``` + +Once we have a running cluster, we can create a `Bundle` using the default CAs which were configured +when trust-manager started up. Since we've installed trust-manager using Helm, our default CA package +contains publicly trusted certificates derived from a Debian container. + +```bash +kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - < 🤔 Wondering why we used `tls.crt` and not `ca.crt`? More details [below](./README.md#preparing-for-production). + +Finally, we'll update our `Bundle` to include our new private CA: + +```bash +kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - < ⚠️ This upgrade process assumes that it's the only thing running. If another user or process changes Helm values +> while you're doing this process, you might overwrite their work. + +First, we'll dump our current Helm values, so we don't lose them: + +```bash +helm get values -n cert-manager trust-manager -oyaml > values.yaml +``` + +Next, if `defaultPackageImage.tag` is already set in `values.yaml`, update it. Otherwise, add it. +You can find the available tags [on `quay.io`](https://quay.io/repository/jetstack/cert-manager-package-debian?tab=tags&tag=latest). + +```yaml +# values.yaml +... +defaultPackageImage: + tag: XYZ +``` + +These versions of the default package image tags are derived directly from the version of the `ca-certificates` package in Debian. + +Finally, apply back the changes, being sure to manually specify the version of trust-manager which is installed, to avoid +also updating the trust-manager controller at the same as the default CA package: + +```bash +# Get the currently installed version. You could do this manually if you find that easier. +TRUST_MANAGER_VER=$(helm list --filter "^trust-manager$" -n cert-manager -ojson | jq -r ".[0].app_version") + +# Check the version makes sense +echo $TRUST_MANAGER_VER + +# Run the upgrade +helm upgrade -f values.yaml -n cert-manager trust-manager jetstack/trust-manager --version $TRUST_MANAGER_VER +``` + +If an incorrect tag is used, your deployment will fail and you'll likely need to use `helm rollback` to get back +to a working state. + +## Preparing for Production + +TLS can be complicated and there are many ways to misuse TLS certificates. + +Here are some potential gotchas here to be aware of before running trust-manager in production. + +If you're planning on running trust-manager in production and you're using more than just the default CA package, +we **strongly** advise you to read and understand this section. It could save you from causing an outage later. + +> ℹ️ These gotchas aren't specific to trust-manager and you could run into any of them with any method of managing TLS trust! + +### Bundling Intermediates + +If you've ever used a Let's Encrypt client such as [Certbot](https://certbot.eff.org/) you'll probably have +seen that it generates several certificate files, such as `cert.pem`, `chain.pem`, and `fullchain.pem`. + +These various files are provided to support various different applications, which might require the certificate +and the chain to be given separately. For most users and applications `fullchain.pem` is the only correct choice. + +Unfortunately the existence of these files has the unfortunate side effect of people sometimes assuming that `cert.pem` +is the correct choice even when `fullchain.pem` would be correct. This means that the rest of the chain will not +be sent when the certificate is used. + +Often, a quick fix that _seems_ to work for this is that clients add the chain to their trust store, which will seem +to fix certificate errors in the short term. It's easy for this kind of "fix" to end up being embedded somewhere as a +solution which others can follow. + +This "fix" is dangerous; it means that the intermediate cannot be safely rotated without all trust stores +which contain it being updated first. + +Intermediates in this case become _de facto_ root certificates, which completely defeats the point of having +intermediate certificates in the first place. + +Avoid using intermediates in any trust store wherever possible unless you're absolutely certain they should be included. +An example of where it might be OK would be cross signing, which is not likely to be required in the general case. + +It would be better to copy just the root certificate to a new `ConfigMap` and use that as a source rather than trusting +an intermediate. + +### cert-manager Integration: `ca.crt` vs `tls.crt` + +If you're pointing trust-manager at a `Secret` containing a cert-manager-issued certificate, you'll see two relevant +fields: `ca.crt` and `tls.crt`. (We're ignoring `tls.key` - trust-manager definitely doesn't need to access that) + +That leads to an obvious question: between `ca.crt` and `tls.crt`, which should I use for trust-manager? + +Unfortunately, it's impossible to say in the general case which field is correct to use, but we can provide guidelines. + +`tls.crt` will generally contain multiple certificates which may not all be issuers and some of which are likely to be +intermediate certificates. If that's the case, you shouldn't use `tls.crt` as a source. (See "Bundling Intermediates" above for details.) + +`ca.crt` might then seem like the more generally correct choice but it's important to bear in mind that it can only ever +be populated on a best-effort basis. The contents of `ca.crt` depend on the `Issuer` being configured correctly, and some +issuer types may not ever be able to provide a useful or correct entry for this field. + +As a rule, you should prefer to create `Bundles` exclusively using root certificates (again, see above), and so you should +only use whichever field has a single root certificate in it. Consider reading below about why you might not want to +actually rely directly on cert-manager-issued certificates. + +### cert-manager Integration: Intentionally Copying CA Certificates + +It's very strange in the Kubernetes world to suggest intentionally adding a step which seems to make automating infrastructure +harder, but in the case of TLS trust stores it can be a wise choice. + +Say you have a cert-manager `Issuer` which has the root certificate you want to trust in `ca.crt`. It's tempting to +use the `Secret` directly and point at `ca.crt`, but a best practice would be to copy that root into a separate `ConfigMap` +(or `Secret`). + +The reason is - as with many TLS gotchas - certificate rotation. If you rotate your issuer such that it's issued from a new root +certificate, trust-manager will see the `Secret` be updated and automatically update your trust bundle to include the new root - +immediately distrusting the old root. + +That means that if any services were still using a certificate issued by the old root, they'll be distrusted and will break. + +Rotation requires that both root certificates are trusted simultaneously for a period, or else that all issued certificates +are rotated either before or at the same time as the old root. + +## Known Issues + +### `kubectl describe` + +The `useDefaultCAs` option hits a corner case inside `kubectl describe` and is rendered as `Use Default C As: true`. This is +purely cosmetic. diff --git a/content/v1.12-docs/projects/trust-manager/api-reference.md b/content/v1.12-docs/projects/trust-manager/api-reference.md new file mode 100644 index 0000000000..994e70ce3b --- /dev/null +++ b/content/v1.12-docs/projects/trust-manager/api-reference.md @@ -0,0 +1,478 @@ +--- +title: trust-manager API Reference +description: "trust-manager API documentation for custom resources" +--- + +Packages: + +- [`trust.cert-manager.io/v1alpha1`](#trustcert-manageriov1alpha1) + +# `trust.cert-manager.io/v1alpha1` + +Resource Types: + + +- [Bundle](#bundle) + + + + +## `Bundle` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringtrust.cert-manager.io/v1alpha1true
kindstringBundletrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + Desired state of the Bundle resource.
+
true
statusobject + Status of the Bundle. This is set and managed automatically.
+
false
+ + +### `Bundle.spec` + + +Desired state of the Bundle resource. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
sources[]object + Sources is a set of references to data whose data will sync to the target.
+
true
targetobject + Target is the target location in all namespaces to sync source data to.
+
true
+ + +### `Bundle.spec.sources[index]` + + +BundleSource is the set of sources whose data will be appended and synced to the BundleTarget in all Namespaces. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
configMapobject + ConfigMap is a reference to a ConfigMap's `data` key, in the trust Namespace.
+
false
inLinestring + InLine is a simple string to append as the source data.
+
false
secretobject + Secret is a reference to a Secrets's `data` key, in the trust Namespace.
+
false
useDefaultCAsboolean + UseDefaultCAs, when true, requests the default CA bundle to be used as a source. Default CAs are available if trust-manager was installed via Helm or was otherwise set up to include a package-injecting init container by using the "--default-package-location" flag when starting the trust-manager controller. If default CAs were not configured at start-up, any request to use the default CAs will fail. The version of the default CA package which is used for a Bundle is stored in the defaultCAPackageVersion field of the Bundle's status field.
+
false
+ + +### `Bundle.spec.sources[index].configMap` + + +ConfigMap is a reference to a ConfigMap's `data` key, in the trust Namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + Key is the key of the entry in the object's `data` field to be used.
+
true
namestring + Name is the name of the source object in the trust Namespace.
+
true
+ + +### `Bundle.spec.sources[index].secret` + + +Secret is a reference to a Secrets's `data` key, in the trust Namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + Key is the key of the entry in the object's `data` field to be used.
+
true
namestring + Name is the name of the source object in the trust Namespace.
+
true
+ + +### `Bundle.spec.target` + + +Target is the target location in all namespaces to sync source data to. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
configMapobject + ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
+
false
namespaceSelectorobject + NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
+
false
+ + +### `Bundle.spec.target.configMap` + + +ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + Key is the key of the entry in the object's `data` field to be used.
+
true
+ + +### `Bundle.spec.target.namespaceSelector` + + +NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchLabelsmap[string]string + MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
+
false
+ + +### `Bundle.status` + + +Status of the Bundle. This is set and managed automatically. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
conditions[]object + List of status conditions to indicate the status of the Bundle. Known condition types are `Bundle`.
+
false
defaultCAVersionstring + DefaultCAPackageVersion, if set and non-empty, indicates the version information which was retrieved when the set of default CAs was requested in the bundle source. This should only be set if useDefaultCAs was set to "true" on a source, and will be the same for the same version of a bundle with identical certificates.
+
false
targetobject + Target is the current Target that the Bundle is attempting or has completed syncing the source data to.
+
false
+ + +### `Bundle.status.conditions[index]` + + +BundleCondition contains condition information for a Bundle. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
statusstring + Status of the condition, one of ('True', 'False', 'Unknown').
+
true
typestring + Type of the condition, known values are (`Synced`).
+
true
lastTransitionTimestring + LastTransitionTime is the timestamp corresponding to the last status change of this condition.
+
+ Format: date-time
+
false
messagestring + Message is a human readable description of the details of the last transition, complementing reason.
+
false
observedGenerationinteger + If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Bundle.
+
+ Format: int64
+
false
reasonstring + Reason is a brief machine readable explanation for the condition's last transition.
+
false
+ + +### `Bundle.status.target` + + +Target is the current Target that the Bundle is attempting or has completed syncing the source data to. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
configMapobject + ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
+
false
namespaceSelectorobject + NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
+
false
+ + +### `Bundle.status.target.configMap` + + +ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + Key is the key of the entry in the object's `data` field to be used.
+
true
+ + +### `Bundle.status.target.namespaceSelector` + + +NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchLabelsmap[string]string + MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
+
false
diff --git a/content/v1.12-docs/reference/README.md b/content/v1.12-docs/reference/README.md new file mode 100644 index 0000000000..473828e355 --- /dev/null +++ b/content/v1.12-docs/reference/README.md @@ -0,0 +1,15 @@ +--- +title: Reference +description: Reference material including TLS terminology, API documentation, and information about the command line flags of the cert-manager components. +--- + +This section contains reference material including TLS terminology, API documentation, and information about the command line flags of the cert-manager components. + +* [TLS Terminology](./tls-terminology.md): + Learn about the TLS terminology used in the cert-manager documentation such as `publicly trusted`, `self-signed`, `root`, `intermediate` and `leaf` _certificate_. + +* [Components / Docker Images](../cli/README.md): + Learn about the command line flags of the cert-manager Docker images: `controller`, `webhook`, `cainjector`, `acmesolver`, which run in containers in your cluster. + +* [API Reference](./api-docs.md): + Learn about the cert-manager API which includes Custom Resources such as Certificate, CertificateRequest, Issuer and ClusterIssuer. diff --git a/content/v1.12-docs/reference/api-docs.md b/content/v1.12-docs/reference/api-docs.md new file mode 100644 index 0000000000..35d0f49d6c --- /dev/null +++ b/content/v1.12-docs/reference/api-docs.md @@ -0,0 +1,5674 @@ +--- +title: API Reference +description: >- + cert-manager API documentation, including Custom Resources such as + Certificate, CertificateRequest, Issuer and ClusterIssuer +--- +

cert-manager API documentation, including various Custom Resource Definitions

+

Packages:

+ +

acme.cert-manager.io/v1

+
+

Package v1 is the v1 version of the API.

+
+

Resource Types:

+ +

Challenge

+
+

Challenge is a type to represent a Challenge request with an ACME server

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ apiVersion +
+ string +
+ acme.cert-manager.io/v1 +
+ kind +
+ string +
+ Challenge +
+ metadata +
+ + Kubernetes meta/v1.ObjectMeta + +
+ Refer to the Kubernetes API documentation for the fields of the + metadata field. +
+ spec +
+ + ChallengeSpec + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ url +
+ string +
+

The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge.

+
+ authorizationURL +
+ string +
+

The URL to the ACME Authorization resource that this challenge is a part of.

+
+ dnsName +
+ string +
+

dnsName is the identifier that this challenge is for, e.g. example.com. If the requested DNSName is a ‘wildcard’, this field MUST be set to the non-wildcard domain, e.g. for *.example.com, it must be example.com.

+
+ wildcard +
+ bool +
+ (Optional) +

wildcard will be true if this challenge is for a wildcard identifier, for example ‘*.example.com’.

+
+ type +
+ + ACMEChallengeType + +
+

The type of ACME challenge this resource represents. One of “HTTP-01” or “DNS-01”.

+
+ token +
+ string +
+

The ACME challenge token for this challenge. This is the raw value returned from the ACME server.

+
+ key +
+ string +
+

+ The ACME challenge key for this challenge For HTTP01 challenges, this is the value that must be responded with to complete the HTTP01 challenge in the format: + <private key JWK thumbprint>.<key from acme server for challenge>. For DNS01 challenges, this is the base64 encoded SHA256 sum of the + <private key JWK thumbprint>.<key from acme server for challenge> + text that must be set as the TXT record content. +

+
+ solver +
+ + ACMEChallengeSolver + +
+

Contains the domain solving configuration that should be used to solve this challenge resource.

+
+ issuerRef +
+ + ObjectReference + +
+

References a properly configured ACME-type Issuer which should be used to create this Challenge. If the Issuer does not exist, processing will be retried. If the Issuer is not an ‘ACME’ Issuer, an error will be returned and the Challenge will be marked as failed.

+
+
+ status +
+ + ChallengeStatus + +
+ (Optional) +
+

Order

+
+

Order is a type to represent an Order with an ACME server

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ apiVersion +
+ string +
+ acme.cert-manager.io/v1 +
+ kind +
+ string +
+ Order +
+ metadata +
+ + Kubernetes meta/v1.ObjectMeta + +
+ Refer to the Kubernetes API documentation for the fields of the + metadata field. +
+ spec +
+ + OrderSpec + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ request +
+ []byte +
+

Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order.

+
+ issuerRef +
+ + ObjectReference + +
+

IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an ‘ACME’ Issuer, an error will be returned and the Order will be marked as failed.

+
+ commonName +
+ string +
+ (Optional) +

CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in dnsNames or ipAddresses. This field must match the corresponding field on the DER encoded CSR.

+
+ dnsNames +
+ []string +
+ (Optional) +

DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.

+
+ ipAddresses +
+ []string +
+ (Optional) +

IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.

+
+ duration +
+ + Kubernetes meta/v1.Duration + +
+ (Optional) +

Duration is the duration for the not after date for the requested certificate. this is set on order creation as pe the ACME spec.

+
+
+ status +
+ + OrderStatus + +
+ (Optional) +
+

ACMEAuthorization

+

(Appears on: OrderStatus)

+
+

ACMEAuthorization contains data returned from the ACME server on an authorization that must be completed in order validate a DNS name on an ACME Order resource.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ url +
+ string +
+

URL is the URL of the Authorization that must be completed

+
+ identifier +
+ string +
+ (Optional) +

Identifier is the DNS name to be validated as part of this authorization

+
+ wildcard +
+ bool +
+ (Optional) +

Wildcard will be true if this authorization is for a wildcard DNS name. If this is true, the identifier will be the non-wildcard version of the DNS name. For example, if ‘*.example.com’ is the DNS name being validated, this field will be ‘true’ and the ‘identifier’ field will be ‘example.com’.

+
+ initialState +
+ + State + +
+ (Optional) +

InitialState is the initial state of the ACME authorization when first fetched from the ACME server. If an Authorization is already ‘valid’, the Order controller will not create a Challenge resource for the authorization. This will occur when working with an ACME server that enables ‘authz reuse’ (such as Let’s Encrypt’s production endpoint). If not set and ‘identifier’ is set, the state is assumed to be pending and a Challenge will be created.

+
+ challenges +
+ + []ACMEChallenge + +
+ (Optional) +

Challenges specifies the challenge types offered by the ACME server. One of these challenge types will be selected when validating the DNS name and an appropriate Challenge resource will be created to perform the ACME challenge process.

+
+

ACMEChallenge

+

(Appears on: ACMEAuthorization)

+
+

Challenge specifies a challenge offered by the ACME server for an Order. An appropriate Challenge resource can be created to perform the ACME challenge process.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ url +
+ string +
+

URL is the URL of this challenge. It can be used to retrieve additional metadata about the Challenge from the ACME server.

+
+ token +
+ string +
+

Token is the token that must be presented for this challenge. This is used to compute the ‘key’ that must also be presented.

+
+ type +
+ string +
+

Type is the type of challenge being offered, e.g. ‘http-01’, ‘dns-01’, ‘tls-sni-01’, etc. This is the raw value retrieved from the ACME server. Only ‘http-01’ and ‘dns-01’ are supported by cert-manager, other values will be ignored.

+
+

ACMEChallengeSolver

+

(Appears on: ACMEIssuer, ChallengeSpec)

+
+

An ACMEChallengeSolver describes how to solve ACME challenges for the issuer it is part of. A selector may be provided to use different solving strategies for different DNS names. Only one of HTTP01 or DNS01 must be provided.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ selector +
+ + CertificateDNSNameSelector + +
+ (Optional) +

Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the ‘default’ solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead.

+
+ http01 +
+ + ACMEChallengeSolverHTTP01 + +
+ (Optional) +

Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. *.example.com) using the HTTP01 challenge mechanism.

+
+ dns01 +
+ + ACMEChallengeSolverDNS01 + +
+ (Optional) +

Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow.

+
+

ACMEChallengeSolverDNS01

+

(Appears on: ACMEChallengeSolver)

+
+

Used to configure a DNS01 challenge provider to be used when solving DNS01 challenges. Only one DNS provider may be configured per solver.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ cnameStrategy +
+ + CNAMEStrategy + +
+ (Optional) +

CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones.

+
+ akamai +
+ + ACMEIssuerDNS01ProviderAkamai + +
+ (Optional) +

Use the Akamai DNS zone management API to manage DNS01 challenge records.

+
+ cloudDNS +
+ + ACMEIssuerDNS01ProviderCloudDNS + +
+ (Optional) +

Use the Google Cloud DNS API to manage DNS01 challenge records.

+
+ cloudflare +
+ + ACMEIssuerDNS01ProviderCloudflare + +
+ (Optional) +

Use the Cloudflare API to manage DNS01 challenge records.

+
+ route53 +
+ + ACMEIssuerDNS01ProviderRoute53 + +
+ (Optional) +

Use the AWS Route53 API to manage DNS01 challenge records.

+
+ azureDNS +
+ + ACMEIssuerDNS01ProviderAzureDNS + +
+ (Optional) +

Use the Microsoft Azure DNS API to manage DNS01 challenge records.

+
+ digitalocean +
+ + ACMEIssuerDNS01ProviderDigitalOcean + +
+ (Optional) +

Use the DigitalOcean DNS API to manage DNS01 challenge records.

+
+ acmeDNS +
+ + ACMEIssuerDNS01ProviderAcmeDNS + +
+ (Optional) +

Use the ‘ACME DNS’ (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records.

+
+ rfc2136 +
+ + ACMEIssuerDNS01ProviderRFC2136 + +
+ (Optional) +

Use RFC2136 (“Dynamic Updates in the Domain Name System”) (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records.

+
+ webhook +
+ + ACMEIssuerDNS01ProviderWebhook + +
+ (Optional) +

Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records.

+
+

ACMEChallengeSolverHTTP01

+

(Appears on: ACMEChallengeSolver)

+
+

ACMEChallengeSolverHTTP01 contains configuration detailing how to solve HTTP01 challenges within a Kubernetes cluster. Typically this is accomplished through creating ‘routes’ of some description that configure ingress controllers to direct traffic to ‘solver pods’, which are responsible for responding to the ACME server’s HTTP requests. Only one of Ingress / Gateway can be specified.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ ingress +
+ + ACMEChallengeSolverHTTP01Ingress + +
+ (Optional) +

The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for ‘/.well-known/acme-challenge/XYZ’ to ‘challenge solver’ pods that are provisioned by cert-manager for each Challenge to be completed.

+
+ gatewayHTTPRoute +
+ + ACMEChallengeSolverHTTP01GatewayHTTPRoute + +
+ (Optional) +

The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future.

+
+

ACMEChallengeSolverHTTP01GatewayHTTPRoute

+

(Appears on: ACMEChallengeSolverHTTP01)

+
+

The ACMEChallengeSolverHTTP01GatewayHTTPRoute solver will create HTTPRoute objects for a Gateway class routing to an ACME challenge solver pod.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ serviceType +
+ + Kubernetes core/v1.ServiceType + +
+ (Optional) +

Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort.

+
+ labels +
+ map[string]string +
+ (Optional) +

Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges.

+
+ parentRefs +
+ []sigs.k8s.io/gateway-api/apis/v1beta1.ParentReference +
+

+ When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: + https://gateway-api.sigs.k8s.io/api-types/httproute/#attaching-to-gateways +

+
+

ACMEChallengeSolverHTTP01Ingress

+

(Appears on: ACMEChallengeSolverHTTP01)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ serviceType +
+ + Kubernetes core/v1.ServiceType + +
+ (Optional) +

Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort.

+
+ class +
+ string +
+ (Optional) +

The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of ‘class’ or ‘name’ may be specified.

+
+ name +
+ string +
+ (Optional) +

The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources.

+
+ podTemplate +
+ + ACMEChallengeSolverHTTP01IngressPodTemplate + +
+ (Optional) +

Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges.

+
+ ingressTemplate +
+ + ACMEChallengeSolverHTTP01IngressTemplate + +
+ (Optional) +

Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges.

+
+

ACMEChallengeSolverHTTP01IngressObjectMeta

+

(Appears on: ACMEChallengeSolverHTTP01IngressTemplate)

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ annotations +
+ map[string]string +
+ (Optional) +

Annotations that should be added to the created ACME HTTP01 solver ingress.

+
+ labels +
+ map[string]string +
+ (Optional) +

Labels that should be added to the created ACME HTTP01 solver ingress.

+
+

ACMEChallengeSolverHTTP01IngressPodObjectMeta

+

(Appears on: ACMEChallengeSolverHTTP01IngressPodTemplate)

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ annotations +
+ map[string]string +
+ (Optional) +

Annotations that should be added to the create ACME HTTP01 solver pods.

+
+ labels +
+ map[string]string +
+ (Optional) +

Labels that should be added to the created ACME HTTP01 solver pods.

+
+

ACMEChallengeSolverHTTP01IngressPodSpec

+

(Appears on: ACMEChallengeSolverHTTP01IngressPodTemplate)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ nodeSelector +
+ map[string]string +
+ (Optional) +

NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node’s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

+
+ affinity +
+ + Kubernetes core/v1.Affinity + +
+ (Optional) +

If specified, the pod’s scheduling constraints

+
+ tolerations +
+ + []Kubernetes core/v1.Toleration + +
+ (Optional) +

If specified, the pod’s tolerations.

+
+ priorityClassName +
+ string +
+ (Optional) +

If specified, the pod’s priorityClassName.

+
+ serviceAccountName +
+ string +
+ (Optional) +

If specified, the pod’s service account

+
+

ACMEChallengeSolverHTTP01IngressPodTemplate

+

(Appears on: ACMEChallengeSolverHTTP01Ingress)

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ metadata +
+ + ACMEChallengeSolverHTTP01IngressPodObjectMeta + +
+ (Optional) +

ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the ‘labels’ and ‘annotations’ fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values.

+
+ spec +
+ + ACMEChallengeSolverHTTP01IngressPodSpec + +
+ (Optional) +

PodSpec defines overrides for the HTTP01 challenge solver pod. Only the ‘priorityClassName’, ‘nodeSelector’, ‘affinity’, ‘serviceAccountName’ and ‘tolerations’ fields are supported currently. All other fields will be ignored.

+
+
+ + + + + + + + + + + + + + + + + + + + + +
+ nodeSelector +
+ map[string]string +
+ (Optional) +

NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node’s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

+
+ affinity +
+ + Kubernetes core/v1.Affinity + +
+ (Optional) +

If specified, the pod’s scheduling constraints

+
+ tolerations +
+ + []Kubernetes core/v1.Toleration + +
+ (Optional) +

If specified, the pod’s tolerations.

+
+ priorityClassName +
+ string +
+ (Optional) +

If specified, the pod’s priorityClassName.

+
+ serviceAccountName +
+ string +
+ (Optional) +

If specified, the pod’s service account

+
+
+

ACMEChallengeSolverHTTP01IngressTemplate

+

(Appears on: ACMEChallengeSolverHTTP01Ingress)

+
+ + + + + + + + + + + + + +
FieldDescription
+ metadata +
+ + ACMEChallengeSolverHTTP01IngressObjectMeta + +
+ (Optional) +

ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the ‘labels’ and ‘annotations’ fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values.

+
+

ACMEChallengeType (string alias)

+

(Appears on: ChallengeSpec)

+
+

The type of ACME challenge. Only HTTP-01 and DNS-01 are supported.

+
+ + + + + + + + + + + + + + + + + +
ValueDescription
+

"DNS-01"

+
+

ACMEChallengeTypeDNS01 denotes a Challenge is of type dns-01 More info: https://letsencrypt.org/docs/challenge-types/#dns-01-challenge

+
+

"HTTP-01"

+
+

ACMEChallengeTypeHTTP01 denotes a Challenge is of type http-01 More info: https://letsencrypt.org/docs/challenge-types/#http-01-challenge

+
+

ACMEExternalAccountBinding

+

(Appears on: ACMEIssuer)

+
+

ACMEExternalAccountBinding is a reference to a CA external account of the ACME server.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ keyID +
+ string +
+

keyID is the ID of the CA key that the External Account is bound to.

+
+ keySecretRef +
+ + SecretKeySelector + +
+

keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes Secret which holds the symmetric MAC key of the External Account Binding. The key is the index string that is paired with the key data in the Secret and should not be confused with the key data itself, or indeed with the External Account Binding keyID above. The secret key stored in the Secret must be un-padded, base64 URL encoded data.

+
+ keyAlgorithm +
+ + HMACKeyAlgorithm + +
+ (Optional) +

Deprecated: keyAlgorithm field exists for historical compatibility reasons and should not be used. The algorithm is now hardcoded to HS256 in golang/x/crypto/acme.

+
+

ACMEIssuer

+

(Appears on: IssuerConfig)

+
+

ACMEIssuer contains the specification for an ACME issuer. This uses the RFC8555 specification to obtain certificates by completing ‘challenges’ to prove ownership of domain identifiers. Earlier draft versions of the ACME specification are not supported.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ email +
+ string +
+ (Optional) +

Email is the email address to be associated with the ACME account. This field is optional, but it is strongly recommended to be set. It will be used to contact you in case of issues with your account or certificates, including expiry notification emails. This field may be updated after the account is initially registered.

+
+ server +
+ string +
+

Server is the URL used to access the ACME server’s ‘directory’ endpoint. For example, for Let’s Encrypt’s staging endpoint, you would use: “https://acme-staging-v02.api.letsencrypt.org/directory”. Only ACME v2 endpoints (i.e. RFC 8555) are supported.

+
+ preferredChain +
+ string +
+ (Optional) +

PreferredChain is the chain to use if the ACME server outputs multiple. PreferredChain is no guarantee that this one gets delivered by the ACME endpoint. For example, for Let’s Encrypt’s DST crosssign you would use: “DST Root CA X3” or “ISRG Root X1” for the newer Let’s Encrypt root CA. This value picks the first certificate bundle in the ACME alternative chains that has a certificate with this value as its issuer’s CN

+
+ caBundle +
+ []byte +
+ (Optional) +

Base64-encoded bundle of PEM CAs which can be used to validate the certificate chain presented by the ACME server. Mutually exclusive with SkipTLSVerify; prefer using CABundle to prevent various kinds of security vulnerabilities. If CABundle and SkipTLSVerify are unset, the system certificate bundle inside the container is used to validate the TLS connection.

+
+ skipTLSVerify +
+ bool +
+ (Optional) +

INSECURE: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have the TLS certificate chain validated. Mutually exclusive with CABundle; prefer using CABundle to prevent various kinds of security vulnerabilities. Only enable this option in development environments. If CABundle and SkipTLSVerify are unset, the system certificate bundle inside the container is used to validate the TLS connection. Defaults to false.

+
+ externalAccountBinding +
+ + ACMEExternalAccountBinding + +
+ (Optional) +

ExternalAccountBinding is a reference to a CA external account of the ACME server. If set, upon registration cert-manager will attempt to associate the given external account credentials with the registered ACME account.

+
+ privateKeySecretRef +
+ + SecretKeySelector + +
+

PrivateKey is the name of a Kubernetes Secret resource that will be used to store the automatically generated ACME account private key. Optionally, a key may be specified to select a specific entry within the named Secret resource. If key is not specified, a default of tls.key will be used.

+
+ solvers +
+ + []ACMEChallengeSolver + +
+ (Optional) +

Solvers is a list of challenge solvers that will be used to solve ACME challenges for the matching domains. Solver configurations must be provided in order to obtain certificates from an ACME server. For more information, see: https://cert-manager.io/docs/configuration/acme/

+
+ disableAccountKeyGeneration +
+ bool +
+ (Optional) +

Enables or disables generating a new ACME account key. If true, the Issuer resource will not request a new account but will expect the account key to be supplied via an existing secret. If false, the cert-manager system will generate a new ACME account key for the Issuer. Defaults to false.

+
+ enableDurationFeature +
+ bool +
+ (Optional) +

Enables requesting a Not After date on certificates that matches the duration of the certificate. This is not supported by all ACME servers like Let’s Encrypt. If set to true when the ACME server does not support it it will create an error on the Order. Defaults to false.

+
+

ACMEIssuerDNS01ProviderAcmeDNS

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

ACMEIssuerDNS01ProviderAcmeDNS is a structure containing the configuration for ACME-DNS servers

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ host +
+ string +
+ accountSecretRef +
+ + SecretKeySelector + +
+

ACMEIssuerDNS01ProviderAkamai

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

ACMEIssuerDNS01ProviderAkamai is a structure containing the DNS configuration for Akamai DNS—Zone Record Management API

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ serviceConsumerDomain +
+ string +
+ clientTokenSecretRef +
+ + SecretKeySelector + +
+ clientSecretSecretRef +
+ + SecretKeySelector + +
+ accessTokenSecretRef +
+ + SecretKeySelector + +
+

ACMEIssuerDNS01ProviderAzureDNS

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

ACMEIssuerDNS01ProviderAzureDNS is a structure containing the configuration for Azure DNS

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ clientID +
+ string +
+ (Optional) +

if both this and ClientSecret are left unset MSI will be used

+
+ clientSecretSecretRef +
+ + SecretKeySelector + +
+ (Optional) +

if both this and ClientID are left unset MSI will be used

+
+ subscriptionID +
+ string +
+

ID of the Azure subscription

+
+ tenantID +
+ string +
+ (Optional) +

when specifying ClientID and ClientSecret then this field is also needed

+
+ resourceGroupName +
+ string +
+

resource group the DNS zone is located in

+
+ hostedZoneName +
+ string +
+ (Optional) +

name of the DNS zone that should be used

+
+ environment +
+ + AzureDNSEnvironment + +
+ (Optional) +

name of the Azure environment (default AzurePublicCloud)

+
+ managedIdentity +
+ + AzureManagedIdentity + +
+ (Optional) +

managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID

+
+

ACMEIssuerDNS01ProviderCloudDNS

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS configuration for Google Cloud DNS

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ serviceAccountSecretRef +
+ + SecretKeySelector + +
+ (Optional) +
+ project +
+ string +
+ hostedZoneName +
+ string +
+ (Optional) +

HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone.

+
+

ACMEIssuerDNS01ProviderCloudflare

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS configuration for Cloudflare. One of apiKeySecretRef or apiTokenSecretRef must be provided.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ email +
+ string +
+ (Optional) +

Email of the account, only required when using API key based authentication.

+
+ apiKeySecretRef +
+ + SecretKeySelector + +
+ (Optional) +

API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.

+
+ apiTokenSecretRef +
+ + SecretKeySelector + +
+ (Optional) +

API token used to authenticate with Cloudflare.

+
+

ACMEIssuerDNS01ProviderDigitalOcean

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

ACMEIssuerDNS01ProviderDigitalOcean is a structure containing the DNS configuration for DigitalOcean Domains

+
+ + + + + + + + + + + + + +
FieldDescription
+ tokenSecretRef +
+ + SecretKeySelector + +
+

ACMEIssuerDNS01ProviderRFC2136

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

ACMEIssuerDNS01ProviderRFC2136 is a structure containing the configuration for RFC2136 DNS

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ nameserver +
+ string +
+

The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required.

+
+ tsigSecretSecretRef +
+ + SecretKeySelector + +
+ (Optional) +

The name of the secret containing the TSIG value. If tsigKeyName is defined, this field is required.

+
+ tsigKeyName +
+ string +
+ (Optional) +

The TSIG Key name configured in the DNS. If tsigSecretSecretRef is defined, this field is required.

+
+ tsigAlgorithm +
+ string +
+ (Optional) +

The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when tsigSecretSecretRef and tsigKeyName are defined. Supported values are (case-insensitive): HMACMD5 (default), HMACSHA1, HMACSHA256 or HMACSHA512.

+
+

ACMEIssuerDNS01ProviderRoute53

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 configuration for AWS

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ accessKeyID +
+ string +
+ (Optional) +

The AccessKeyID is used for authentication. Cannot be set when SecretAccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials

+
+ accessKeyIDSecretRef +
+ + SecretKeySelector + +
+ (Optional) +

The SecretAccessKey is used for authentication. If set, pull the AWS access key ID from a key within a Kubernetes Secret. Cannot be set when AccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials

+
+ secretAccessKeySecretRef +
+ + SecretKeySelector + +
+ (Optional) +

The SecretAccessKey is used for authentication. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials

+
+ role +
+ string +
+ (Optional) +

Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata

+
+ hostedZoneID +
+ string +
+ (Optional) +

If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call.

+
+ region +
+ string +
+

Always set the region when using AccessKeyID and SecretAccessKey

+
+

ACMEIssuerDNS01ProviderWebhook

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

ACMEIssuerDNS01ProviderWebhook specifies configuration for a webhook DNS01 provider, including where to POST ChallengePayload resources.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ groupName +
+ string +
+

The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation.

+
+ solverName +
+ string +
+

The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. ‘cloudflare’.

+
+ config +
+ + Kubernetes apiextensions/v1.JSON + +
+ (Optional) +

Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation’s documentation.

+
+

ACMEIssuerStatus

+

(Appears on: IssuerStatus)

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ uri +
+ string +
+ (Optional) +

URI is the unique account identifier, which can also be used to retrieve account details from the CA

+
+ lastRegisteredEmail +
+ string +
+ (Optional) +

LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer

+
+

AzureDNSEnvironment (string alias)

+

(Appears on: ACMEIssuerDNS01ProviderAzureDNS)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ValueDescription
+

"AzureChinaCloud"

+
+

"AzureGermanCloud"

+
+

"AzurePublicCloud"

+
+

"AzureUSGovernmentCloud"

+
+

AzureManagedIdentity

+

(Appears on: ACMEIssuerDNS01ProviderAzureDNS)

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ clientID +
+ string +
+ (Optional) +

client ID of the managed identity, can not be used at the same time as resourceID

+
+ resourceID +
+ string +
+ (Optional) +

resource ID of the managed identity, can not be used at the same time as clientID

+
+

CNAMEStrategy (string alias)

+

(Appears on: ACMEChallengeSolverDNS01)

+
+

CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. By default, the None strategy will be applied (i.e. do not follow CNAMEs).

+
+

CertificateDNSNameSelector

+

(Appears on: ACMEChallengeSolver)

+
+

CertificateDNSNameSelector selects certificates using a label selector, and can optionally select individual DNS names within those certificates. If both MatchLabels and DNSNames are empty, this selector will match all certificates and DNS names within them.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ matchLabels +
+ map[string]string +
+ (Optional) +

A label selector that is used to refine the set of certificate’s that this challenge solver will apply to.

+
+ dnsNames +
+ []string +
+ (Optional) +

List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected.

+
+ dnsZones +
+ []string +
+ (Optional) +

List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected.

+
+

ChallengeSpec

+

(Appears on: Challenge)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ url +
+ string +
+

The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge.

+
+ authorizationURL +
+ string +
+

The URL to the ACME Authorization resource that this challenge is a part of.

+
+ dnsName +
+ string +
+

dnsName is the identifier that this challenge is for, e.g. example.com. If the requested DNSName is a ‘wildcard’, this field MUST be set to the non-wildcard domain, e.g. for *.example.com, it must be example.com.

+
+ wildcard +
+ bool +
+ (Optional) +

wildcard will be true if this challenge is for a wildcard identifier, for example ‘*.example.com’.

+
+ type +
+ + ACMEChallengeType + +
+

The type of ACME challenge this resource represents. One of “HTTP-01” or “DNS-01”.

+
+ token +
+ string +
+

The ACME challenge token for this challenge. This is the raw value returned from the ACME server.

+
+ key +
+ string +
+

+ The ACME challenge key for this challenge For HTTP01 challenges, this is the value that must be responded with to complete the HTTP01 challenge in the format: + <private key JWK thumbprint>.<key from acme server for challenge>. For DNS01 challenges, this is the base64 encoded SHA256 sum of the + <private key JWK thumbprint>.<key from acme server for challenge> + text that must be set as the TXT record content. +

+
+ solver +
+ + ACMEChallengeSolver + +
+

Contains the domain solving configuration that should be used to solve this challenge resource.

+
+ issuerRef +
+ + ObjectReference + +
+

References a properly configured ACME-type Issuer which should be used to create this Challenge. If the Issuer does not exist, processing will be retried. If the Issuer is not an ‘ACME’ Issuer, an error will be returned and the Challenge will be marked as failed.

+
+

ChallengeStatus

+

(Appears on: Challenge)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ processing +
+ bool +
+ (Optional) +

Used to denote whether this challenge should be processed or not. This field will only be set to true by the ‘scheduling’ component. It will only be set to false by the ‘challenges’ controller, after the challenge has reached a final state or timed out. If this field is set to false, the challenge controller will not take any more action.

+
+ presented +
+ bool +
+ (Optional) +

presented will be set to true if the challenge values for this challenge are currently ‘presented’. This does not imply the self check is passing. Only that the values have been ‘submitted’ for the appropriate challenge mechanism (i.e. the DNS01 TXT record has been presented, or the HTTP01 configuration has been configured).

+
+ reason +
+ string +
+ (Optional) +

Contains human readable information on why the Challenge is in the current state.

+
+ state +
+ + State + +
+ (Optional) +

Contains the current ‘state’ of the challenge. If not set, the state of the challenge is unknown.

+
+

HMACKeyAlgorithm (string alias)

+

(Appears on: ACMEExternalAccountBinding)

+
+

HMACKeyAlgorithm is the name of a key algorithm used for HMAC encryption

+
+ + + + + + + + + + + + + + + + + + + + + +
ValueDescription
+

"HS256"

+
+

"HS384"

+
+

"HS512"

+
+

OrderSpec

+

(Appears on: Order)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ request +
+ []byte +
+

Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order.

+
+ issuerRef +
+ + ObjectReference + +
+

IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an ‘ACME’ Issuer, an error will be returned and the Order will be marked as failed.

+
+ commonName +
+ string +
+ (Optional) +

CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in dnsNames or ipAddresses. This field must match the corresponding field on the DER encoded CSR.

+
+ dnsNames +
+ []string +
+ (Optional) +

DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.

+
+ ipAddresses +
+ []string +
+ (Optional) +

IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.

+
+ duration +
+ + Kubernetes meta/v1.Duration + +
+ (Optional) +

Duration is the duration for the not after date for the requested certificate. this is set on order creation as pe the ACME spec.

+
+

OrderStatus

+

(Appears on: Order)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ url +
+ string +
+ (Optional) +

URL of the Order. This will initially be empty when the resource is first created. The Order controller will populate this field when the Order is first processed. This field will be immutable after it is initially set.

+
+ finalizeURL +
+ string +
+ (Optional) +

FinalizeURL of the Order. This is used to obtain certificates for this order once it has been completed.

+
+ authorizations +
+ + []ACMEAuthorization + +
+ (Optional) +

Authorizations contains data returned from the ACME server on what authorizations must be completed in order to validate the DNS names specified on the Order.

+
+ certificate +
+ []byte +
+ (Optional) +

Certificate is a copy of the PEM encoded certificate for this Order. This field will be populated after the order has been successfully finalized with the ACME server, and the order has transitioned to the ‘valid’ state.

+
+ state +
+ + State + +
+ (Optional) +

State contains the current state of this Order resource. States ‘success’ and ‘expired’ are ‘final’

+
+ reason +
+ string +
+ (Optional) +

Reason optionally provides more information about a why the order is in the current state.

+
+ failureTime +
+ + Kubernetes meta/v1.Time + +
+ (Optional) +

FailureTime stores the time that this order failed. This is used to influence garbage collection and back-off.

+
+

State (string alias)

+

(Appears on: ACMEAuthorization, ChallengeStatus, OrderStatus)

+
+

+ State represents the state of an ACME resource, such as an Order. The possible options here map to the corresponding values in the ACME specification. Full details of these values can be found here: https://tools.ietf.org/html/draft-ietf-acme-acme-15#section-7.1.6 + Clients utilising this type must also gracefully handle unknown values, as the contents of this enumeration may be added to over time. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueDescription
+

"errored"

+
+

Errored signifies that the ACME resource has errored for some reason. This is a catch-all state, and is used for marking internal cert-manager errors such as validation failures. This is a final state.

+
+

"expired"

+
+

Expired signifies that an ACME resource has expired. If an Order is marked ‘Expired’, one of its validations may have expired or the Order itself. This is a final state.

+
+

"invalid"

+
+

Invalid signifies that an ACME resource is invalid for some reason. If an Order is marked ‘invalid’, one of its validations be have invalid for some reason. This is a final state.

+
+

"pending"

+
+

Pending signifies that an ACME resource is still pending and is not yet ready. If an Order is marked ‘Pending’, the validations for that Order are still in progress. This is a transient state.

+
+

"processing"

+
+

Processing signifies that an ACME resource is being processed by the server. If an Order is marked ‘Processing’, the validations for that Order are currently being processed. This is a transient state.

+
+

"ready"

+
+

Ready signifies that an ACME resource is in a ready state. If an order is ‘ready’, all of its challenges have been completed successfully and the order is ready to be finalized. Once finalized, it will transition to the Valid state. This is a transient state.

+
+

""

+
+

Unknown is not a real state as part of the ACME spec. It is used to represent an unrecognised value.

+
+

"valid"

+
+

Valid signifies that an ACME resource is in a valid state. If an order is ‘valid’, it has been finalized with the ACME server and the certificate can be retrieved from the ACME server using the certificate URL stored in the Order’s status subresource. This is a final state.

+
+
+

cert-manager.io/v1

+
+

Package v1 is the v1 version of the API.

+
+

Resource Types:

+ +

Certificate

+
+

A Certificate resource should be created to ensure an up to date and signed x509 certificate is stored in the Kubernetes Secret resource named in spec.secretName.

+

The stored certificate will be renewed before it expires (as configured by spec.renewBefore).

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ apiVersion +
+ string +
+ cert-manager.io/v1 +
+ kind +
+ string +
+ Certificate +
+ metadata +
+ + Kubernetes meta/v1.ObjectMeta + +
+ Refer to the Kubernetes API documentation for the fields of the + metadata field. +
+ spec +
+ + CertificateSpec + +
+

Desired state of the Certificate resource.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ subject +
+ + X509Subject + +
+ (Optional) +

Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name).

+
+ literalSubject +
+ string +
+ (Optional) +

LiteralSubject is an LDAP formatted string that represents the X.509 Subject field. Use this instead of the Subject field if you need to ensure the correct ordering of the RDN sequence, such as when issuing certs for LDAP authentication. See https://github.com/cert-manager/cert-manager/issues/3203, https://github.com/cert-manager/cert-manager/issues/4424. This field is alpha level and is only supported by cert-manager installations where LiteralCertificateSubject feature gate is enabled on both cert-manager controller and webhook.

+
+ commonName +
+ string +
+ (Optional) +

CommonName is a common name to be used on the Certificate. The CommonName should have a length of 64 characters or fewer to avoid generating invalid CSRs. This value is ignored by TLS clients when any subject alt name is set. This is x509 behaviour: https://tools.ietf.org/html/rfc6125#section-6.4.4

+
+ duration +
+ + Kubernetes meta/v1.Duration + +
+ (Optional) +

The requested ‘duration’ (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. If unset this defaults to 90 days. Certificate will be renewed either 23 through its duration or renewBefore period before its expiry, whichever is later. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration

+
+ renewBefore +
+ + Kubernetes meta/v1.Duration + +
+ (Optional) +

+ How long before the currently issued certificate’s expiry cert-manager should renew the certificate. The default is 23 of the issued certificate’s duration. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration + https://golang.org/pkg/time/#ParseDuration +

+
+ dnsNames +
+ []string +
+ (Optional) +

DNSNames is a list of DNS subjectAltNames to be set on the Certificate.

+
+ ipAddresses +
+ []string +
+ (Optional) +

IPAddresses is a list of IP address subjectAltNames to be set on the Certificate.

+
+ uris +
+ []string +
+ (Optional) +

URIs is a list of URI subjectAltNames to be set on the Certificate.

+
+ emailAddresses +
+ []string +
+ (Optional) +

EmailAddresses is a list of email subjectAltNames to be set on the Certificate.

+
+ secretName +
+ string +
+

SecretName is the name of the secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer.

+
+ secretTemplate +
+ + CertificateSecretTemplate + +
+ (Optional) +

SecretTemplate defines annotations and labels to be copied to the Certificate’s Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate’s Secret.

+
+ keystores +
+ + CertificateKeystores + +
+ (Optional) +

+ Keystores configures additional keystore output formats stored in the + secretName Secret resource. +

+
+ issuerRef +
+ + ObjectReference + +
+

IssuerRef is a reference to the issuer for this certificate. If the kind field is not set, or set to Issuer, an Issuer resource with the given name in the same namespace as the Certificate will be used. If the kind field is set to ClusterIssuer, a ClusterIssuer with the provided name will be used. The name field in this stanza is required at all times.

+
+ isCA +
+ bool +
+ (Optional) +

IsCA will mark this Certificate as valid for certificate signing. This will automatically add the cert sign usage to the list of usages.

+
+ usages +
+ + []KeyUsage + +
+ (Optional) +

Usages is the set of x509 usages that are requested for the certificate. Defaults to digital signature and key encipherment if not specified.

+
+ privateKey +
+ + CertificatePrivateKey + +
+ (Optional) +

Options to control private keys used for the Certificate.

+
+ encodeUsagesInRequest +
+ bool +
+ (Optional) +

EncodeUsagesInRequest controls whether key usages should be present in the CertificateRequest

+
+ revisionHistoryLimit +
+ int32 +
+ (Optional) +

revisionHistoryLimit is the maximum number of CertificateRequest revisions that are maintained in the Certificate’s history. Each revision represents a single CertificateRequest created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. If set, revisionHistoryLimit must be a value of 1 or greater. If unset (nil), revisions will not be garbage collected. Default value is nil.

+
+ additionalOutputFormats +
+ + []CertificateAdditionalOutputFormat + +
+ (Optional) +

+ AdditionalOutputFormats defines extra output formats of the private key and signed certificate chain to be written to this Certificate’s target Secret. This is an Alpha Feature and is only enabled with the + --feature-gates=AdditionalCertificateOutputFormats=true option on both the controller and webhook components. +

+
+
+ status +
+ + CertificateStatus + +
+ (Optional) +

Status of the Certificate. This is set and managed automatically.

+
+

CertificateRequest

+
+

A CertificateRequest is used to request a signed certificate from one of the configured issuers.

+

+ All fields within the CertificateRequest’s spec are immutable after creation. A CertificateRequest will either succeed or fail, as denoted by its status.state + field. +

+

A CertificateRequest is a one-shot resource, meaning it represents a single point in time request for a certificate and cannot be re-used.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ apiVersion +
+ string +
+ cert-manager.io/v1 +
+ kind +
+ string +
+ CertificateRequest +
+ metadata +
+ + Kubernetes meta/v1.ObjectMeta + +
+ Refer to the Kubernetes API documentation for the fields of the + metadata field. +
+ spec +
+ + CertificateRequestSpec + +
+

Desired state of the CertificateRequest resource.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ duration +
+ + Kubernetes meta/v1.Duration + +
+ (Optional) +

The requested ‘duration’ (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types.

+
+ issuerRef +
+ + ObjectReference + +
+

IssuerRef is a reference to the issuer for this CertificateRequest. If the kind field is not set, or set to Issuer, an Issuer resource with the given name in the same namespace as the CertificateRequest will be used. If the kind field is set to ClusterIssuer, a ClusterIssuer with the provided name will be used. The name field in this stanza is required at all times. The group field refers to the API group of the issuer which defaults to cert-manager.io if empty.

+
+ request +
+ []byte +
+

The PEM-encoded x509 certificate signing request to be submitted to the CA for signing.

+
+ isCA +
+ bool +
+ (Optional) +

IsCA will request to mark the certificate as valid for certificate signing when submitting to the issuer. This will automatically add the cert sign usage to the list of usages.

+
+ usages +
+ + []KeyUsage + +
+ (Optional) +

Usages is the set of x509 usages that are requested for the certificate. If usages are set they SHOULD be encoded inside the CSR spec Defaults to digital signature and key encipherment if not specified.

+
+ username +
+ string +
+ (Optional) +

Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

+
+ uid +
+ string +
+ (Optional) +

UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

+
+ groups +
+ []string +
+ (Optional) +

Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

+
+ extra +
+ map[string][]string +
+ (Optional) +

Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

+
+
+ status +
+ + CertificateRequestStatus + +
+ (Optional) +

Status of the CertificateRequest. This is set and managed automatically.

+
+

ClusterIssuer

+
+

A ClusterIssuer represents a certificate issuing authority which can be referenced as part of issuerRef fields. It is similar to an Issuer, however it is cluster-scoped and therefore can be referenced by resources that exist in any namespace, not just the same namespace as the referent.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ apiVersion +
+ string +
+ cert-manager.io/v1 +
+ kind +
+ string +
+ ClusterIssuer +
+ metadata +
+ + Kubernetes meta/v1.ObjectMeta + +
+ Refer to the Kubernetes API documentation for the fields of the + metadata field. +
+ spec +
+ + IssuerSpec + +
+

Desired state of the ClusterIssuer resource.

+
+
+ + + + + +
+ IssuerConfig +
+ + IssuerConfig + +
+

(Members of IssuerConfig are embedded into this type.)

+
+
+ status +
+ + IssuerStatus + +
+ (Optional) +

Status of the ClusterIssuer. This is set and managed automatically.

+
+

Issuer

+
+

An Issuer represents a certificate issuing authority which can be referenced as part of issuerRef fields. It is scoped to a single namespace and can therefore only be referenced by resources within the same namespace.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ apiVersion +
+ string +
+ cert-manager.io/v1 +
+ kind +
+ string +
+ Issuer +
+ metadata +
+ + Kubernetes meta/v1.ObjectMeta + +
+ Refer to the Kubernetes API documentation for the fields of the + metadata field. +
+ spec +
+ + IssuerSpec + +
+

Desired state of the Issuer resource.

+
+
+ + + + + +
+ IssuerConfig +
+ + IssuerConfig + +
+

(Members of IssuerConfig are embedded into this type.)

+
+
+ status +
+ + IssuerStatus + +
+ (Optional) +

Status of the Issuer. This is set and managed automatically.

+
+

CAIssuer

+

(Appears on: IssuerConfig)

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ secretName +
+ string +
+

SecretName is the name of the secret used to sign Certificates issued by this Issuer.

+
+ crlDistributionPoints +
+ []string +
+ (Optional) +

The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set, certificates will be issued without distribution points set.

+
+ ocspServers +
+ []string +
+ (Optional) +

The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be “http://ocsp.int-x3.letsencrypt.org”.

+
+

CertificateAdditionalOutputFormat

+

(Appears on: CertificateSpec)

+
+

CertificateAdditionalOutputFormat defines an additional output format of a Certificate resource. These contain supplementary data formats of the signed certificate chain and paired private key.

+
+ + + + + + + + + + + + + +
FieldDescription
+ type +
+ + CertificateOutputFormatType + +
+

Type is the name of the format type that should be written to the Certificate’s target Secret.

+
+

CertificateCondition

+

(Appears on: CertificateStatus)

+
+

CertificateCondition contains condition information for an Certificate.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ type +
+ + CertificateConditionType + +
+

Type of the condition, known values are (Ready, Issuing).

+
+ status +
+ + ConditionStatus + +
+

Status of the condition, one of (True, False, Unknown).

+
+ lastTransitionTime +
+ + Kubernetes meta/v1.Time + +
+ (Optional) +

LastTransitionTime is the timestamp corresponding to the last status change of this condition.

+
+ reason +
+ string +
+ (Optional) +

Reason is a brief machine readable explanation for the condition’s last transition.

+
+ message +
+ string +
+ (Optional) +

Message is a human readable description of the details of the last transition, complementing reason.

+
+ observedGeneration +
+ int64 +
+ (Optional) +

If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Certificate.

+
+

CertificateConditionType (string alias)

+

(Appears on: CertificateCondition)

+
+

CertificateConditionType represents an Certificate condition value.

+
+ + + + + + + + + + + + + + + + + +
ValueDescription
+

"Issuing"

+
+

+ A condition added to Certificate resources when an issuance is required. This condition will be automatically added and set to true if: * No keypair data exists in the target Secret * The data stored in the Secret cannot be decoded * The private key and certificate do not have matching public keys * If a CertificateRequest for the current revision exists and the certificate data stored in the Secret does not match the + status.certificate on the CertificateRequest. * If no CertificateRequest resource exists for the current revision, the options on the Certificate resource are compared against the x509 data in the Secret, similar to what’s done in earlier versions. If there is a mismatch, an issuance is triggered. This condition may also be added by external API consumers to trigger a re-issuance manually for any other reason. +

+

It will be removed by the ‘issuing’ controller upon completing issuance.

+
+

"Ready"

+
+

CertificateConditionReady indicates that a certificate is ready for use. This is defined as: - The target secret exists - The target secret contains a certificate that has not expired - The target secret contains a private key valid for the certificate - The commonName and dnsNames attributes match those specified on the Certificate

+
+

CertificateKeystores

+

(Appears on: CertificateSpec)

+
+

CertificateKeystores configures additional keystore output formats to be created in the Certificate’s output Secret.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ jks +
+ + JKSKeystore + +
+ (Optional) +

+ JKS configures options for storing a JKS keystore in the + spec.secretName Secret resource. +

+
+ pkcs12 +
+ + PKCS12Keystore + +
+ (Optional) +

+ PKCS12 configures options for storing a PKCS12 keystore in the + spec.secretName Secret resource. +

+
+

CertificateOutputFormatType (string alias)

+

(Appears on: CertificateAdditionalOutputFormat)

+
+

+ CertificateOutputFormatType specifies which additional output formats should be written to the Certificate’s target Secret. Allowed values are DER or CombinedPEM. When Type is set to DER an additional entry key.der will be written to the Secret, containing the binary format of the private key. When Type is set to CombinedPEM an additional entry tls-combined.pem + will be written to the Secret, containing the PEM formatted private key and signed certificate chain (tls.key + tls.crt concatenated). +

+
+ + + + + + + + + + + + + + + + + +
ValueDescription
+

"CombinedPEM"

+
+

+ CertificateOutputFormatCombinedPEM writes the Certificate’s signed certificate chain and private key, in PEM format, to the + tls-combined.pem target Secret Data key. The value at this key will include the private key PEM document, followed by at least one new line character, followed by the chain of signed certificate PEM documents (<private key> + \n + <signed certificate chain>). +

+
+

"DER"

+
+

CertificateOutputFormatDER writes the Certificate’s private key in DER binary format to the key.der target Secret Data key.

+
+

CertificatePrivateKey

+

(Appears on: CertificateSpec)

+
+

CertificatePrivateKey contains configuration options for private keys used by the Certificate controller. This allows control of how private keys are rotated.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ rotationPolicy +
+ + PrivateKeyRotationPolicy + +
+ (Optional) +

RotationPolicy controls how private keys should be regenerated when a re-issuance is being processed. If set to Never, a private key will only be generated if one does not already exist in the target spec.secretName. If one does exists but it does not have the correct algorithm or size, a warning will be raised to await user intervention. If set to Always, a private key matching the specified requirements will be generated whenever a re-issuance occurs. Default is ‘Never’ for backward compatibility.

+
+ encoding +
+ + PrivateKeyEncoding + +
+ (Optional) +

The private key cryptography standards (PKCS) encoding for this certificate’s private key to be encoded in. If provided, allowed values are PKCS1 and PKCS8 standing for PKCS#1 and PKCS#8, respectively. Defaults to PKCS1 if not specified.

+
+ algorithm +
+ + PrivateKeyAlgorithm + +
+ (Optional) +

Algorithm is the private key algorithm of the corresponding private key for this certificate. If provided, allowed values are either RSA,Ed25519 or ECDSA If algorithm is specified and size is not provided, key size of 256 will be used for ECDSA key algorithm and key size of 2048 will be used for RSA key algorithm. key size is ignored when using the Ed25519 key algorithm.

+
+ size +
+ int +
+ (Optional) +

Size is the key bit size of the corresponding private key for this certificate. If algorithm is set to RSA, valid values are 2048, 4096 or 8192, and will default to 2048 if not specified. If algorithm is set to ECDSA, valid values are 256, 384 or 521, and will default to 256 if not specified. If algorithm is set to Ed25519, Size is ignored. No other values are allowed.

+
+

CertificateRequestCondition

+

(Appears on: CertificateRequestStatus)

+
+

CertificateRequestCondition contains condition information for a CertificateRequest.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ type +
+ + CertificateRequestConditionType + +
+

Type of the condition, known values are (Ready, InvalidRequest,Approved, Denied).

+
+ status +
+ + ConditionStatus + +
+

Status of the condition, one of (True, False, Unknown).

+
+ lastTransitionTime +
+ + Kubernetes meta/v1.Time + +
+ (Optional) +

LastTransitionTime is the timestamp corresponding to the last status change of this condition.

+
+ reason +
+ string +
+ (Optional) +

Reason is a brief machine readable explanation for the condition’s last transition.

+
+ message +
+ string +
+ (Optional) +

Message is a human readable description of the details of the last transition, complementing reason.

+
+

CertificateRequestConditionType (string alias)

+

(Appears on: CertificateRequestCondition)

+
+

CertificateRequestConditionType represents an Certificate condition value.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ValueDescription
+

"Approved"

+
+

+ CertificateRequestConditionApproved indicates that a certificate request is approved and ready for signing. Condition must never have a status of + False, and cannot be modified once set. Cannot be set alongside Denied. +

+
+

"Denied"

+
+

+ CertificateRequestConditionDenied indicates that a certificate request is denied, and must never be signed. Condition must never have a status of + False, and cannot be modified once set. Cannot be set alongside Approved. +

+
+

"InvalidRequest"

+
+

CertificateRequestConditionInvalidRequest indicates that a certificate signer has refused to sign the request due to at least one of the input parameters being invalid. Additional information about why the request was rejected can be found in the reason and message fields.

+
+

"Ready"

+
+

CertificateRequestConditionReady indicates that a certificate is ready for use. This is defined as: - The target certificate exists in CertificateRequest.Status

+
+

CertificateRequestSpec

+

(Appears on: CertificateRequest)

+
+

CertificateRequestSpec defines the desired state of CertificateRequest

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ duration +
+ + Kubernetes meta/v1.Duration + +
+ (Optional) +

The requested ‘duration’ (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types.

+
+ issuerRef +
+ + ObjectReference + +
+

IssuerRef is a reference to the issuer for this CertificateRequest. If the kind field is not set, or set to Issuer, an Issuer resource with the given name in the same namespace as the CertificateRequest will be used. If the kind field is set to ClusterIssuer, a ClusterIssuer with the provided name will be used. The name field in this stanza is required at all times. The group field refers to the API group of the issuer which defaults to cert-manager.io if empty.

+
+ request +
+ []byte +
+

The PEM-encoded x509 certificate signing request to be submitted to the CA for signing.

+
+ isCA +
+ bool +
+ (Optional) +

IsCA will request to mark the certificate as valid for certificate signing when submitting to the issuer. This will automatically add the cert sign usage to the list of usages.

+
+ usages +
+ + []KeyUsage + +
+ (Optional) +

Usages is the set of x509 usages that are requested for the certificate. If usages are set they SHOULD be encoded inside the CSR spec Defaults to digital signature and key encipherment if not specified.

+
+ username +
+ string +
+ (Optional) +

Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

+
+ uid +
+ string +
+ (Optional) +

UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

+
+ groups +
+ []string +
+ (Optional) +

Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

+
+ extra +
+ map[string][]string +
+ (Optional) +

Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

+
+

CertificateRequestStatus

+

(Appears on: CertificateRequest)

+
+

CertificateRequestStatus defines the observed state of CertificateRequest and resulting signed certificate.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ conditions +
+ + []CertificateRequestCondition + +
+ (Optional) +

List of status conditions to indicate the status of a CertificateRequest. Known condition types are Ready and InvalidRequest.

+
+ certificate +
+ []byte +
+ (Optional) +

+ The PEM encoded x509 certificate resulting from the certificate signing request. If not set, the CertificateRequest has either not been completed or has failed. More information on failure can be found by checking the + conditions field. +

+
+ ca +
+ []byte +
+ (Optional) +

The PEM encoded x509 certificate of the signer, also known as the CA (Certificate Authority). This is set on a best-effort basis by different issuers. If not set, the CA is assumed to be unknown/not available.

+
+ failureTime +
+ + Kubernetes meta/v1.Time + +
+ (Optional) +

FailureTime stores the time that this CertificateRequest failed. This is used to influence garbage collection and back-off.

+
+

CertificateSecretTemplate

+

(Appears on: CertificateSpec)

+
+

CertificateSecretTemplate defines the default labels and annotations to be copied to the Kubernetes Secret resource named in CertificateSpec.secretName.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ annotations +
+ map[string]string +
+ (Optional) +

Annotations is a key value map to be copied to the target Kubernetes Secret.

+
+ labels +
+ map[string]string +
+ (Optional) +

Labels is a key value map to be copied to the target Kubernetes Secret.

+
+

CertificateSpec

+

(Appears on: Certificate)

+
+

CertificateSpec defines the desired state of Certificate. A valid Certificate requires at least one of a CommonName, DNSName, or URISAN to be valid.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ subject +
+ + X509Subject + +
+ (Optional) +

Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name).

+
+ literalSubject +
+ string +
+ (Optional) +

LiteralSubject is an LDAP formatted string that represents the X.509 Subject field. Use this instead of the Subject field if you need to ensure the correct ordering of the RDN sequence, such as when issuing certs for LDAP authentication. See https://github.com/cert-manager/cert-manager/issues/3203, https://github.com/cert-manager/cert-manager/issues/4424. This field is alpha level and is only supported by cert-manager installations where LiteralCertificateSubject feature gate is enabled on both cert-manager controller and webhook.

+
+ commonName +
+ string +
+ (Optional) +

CommonName is a common name to be used on the Certificate. The CommonName should have a length of 64 characters or fewer to avoid generating invalid CSRs. This value is ignored by TLS clients when any subject alt name is set. This is x509 behaviour: https://tools.ietf.org/html/rfc6125#section-6.4.4

+
+ duration +
+ + Kubernetes meta/v1.Duration + +
+ (Optional) +

The requested ‘duration’ (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. If unset this defaults to 90 days. Certificate will be renewed either 23 through its duration or renewBefore period before its expiry, whichever is later. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration

+
+ renewBefore +
+ + Kubernetes meta/v1.Duration + +
+ (Optional) +

+ How long before the currently issued certificate’s expiry cert-manager should renew the certificate. The default is 23 of the issued certificate’s duration. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration + https://golang.org/pkg/time/#ParseDuration +

+
+ dnsNames +
+ []string +
+ (Optional) +

DNSNames is a list of DNS subjectAltNames to be set on the Certificate.

+
+ ipAddresses +
+ []string +
+ (Optional) +

IPAddresses is a list of IP address subjectAltNames to be set on the Certificate.

+
+ uris +
+ []string +
+ (Optional) +

URIs is a list of URI subjectAltNames to be set on the Certificate.

+
+ emailAddresses +
+ []string +
+ (Optional) +

EmailAddresses is a list of email subjectAltNames to be set on the Certificate.

+
+ secretName +
+ string +
+

SecretName is the name of the secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer.

+
+ secretTemplate +
+ + CertificateSecretTemplate + +
+ (Optional) +

SecretTemplate defines annotations and labels to be copied to the Certificate’s Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate’s Secret.

+
+ keystores +
+ + CertificateKeystores + +
+ (Optional) +

+ Keystores configures additional keystore output formats stored in the + secretName Secret resource. +

+
+ issuerRef +
+ + ObjectReference + +
+

IssuerRef is a reference to the issuer for this certificate. If the kind field is not set, or set to Issuer, an Issuer resource with the given name in the same namespace as the Certificate will be used. If the kind field is set to ClusterIssuer, a ClusterIssuer with the provided name will be used. The name field in this stanza is required at all times.

+
+ isCA +
+ bool +
+ (Optional) +

IsCA will mark this Certificate as valid for certificate signing. This will automatically add the cert sign usage to the list of usages.

+
+ usages +
+ + []KeyUsage + +
+ (Optional) +

Usages is the set of x509 usages that are requested for the certificate. Defaults to digital signature and key encipherment if not specified.

+
+ privateKey +
+ + CertificatePrivateKey + +
+ (Optional) +

Options to control private keys used for the Certificate.

+
+ encodeUsagesInRequest +
+ bool +
+ (Optional) +

EncodeUsagesInRequest controls whether key usages should be present in the CertificateRequest

+
+ revisionHistoryLimit +
+ int32 +
+ (Optional) +

revisionHistoryLimit is the maximum number of CertificateRequest revisions that are maintained in the Certificate’s history. Each revision represents a single CertificateRequest created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. If set, revisionHistoryLimit must be a value of 1 or greater. If unset (nil), revisions will not be garbage collected. Default value is nil.

+
+ additionalOutputFormats +
+ + []CertificateAdditionalOutputFormat + +
+ (Optional) +

+ AdditionalOutputFormats defines extra output formats of the private key and signed certificate chain to be written to this Certificate’s target Secret. This is an Alpha Feature and is only enabled with the + --feature-gates=AdditionalCertificateOutputFormats=true option on both the controller and webhook components. +

+
+

CertificateStatus

+

(Appears on: Certificate)

+
+

CertificateStatus defines the observed state of Certificate

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ conditions +
+ + []CertificateCondition + +
+ (Optional) +

List of status conditions to indicate the status of certificates. Known condition types are Ready and Issuing.

+
+ lastFailureTime +
+ + Kubernetes meta/v1.Time + +
+ (Optional) +

LastFailureTime is the time as recorded by the Certificate controller of the most recent failure to complete a CertificateRequest for this Certificate resource. If set, cert-manager will not re-request another Certificate until 1 hour has elapsed from this time.

+
+ notBefore +
+ + Kubernetes meta/v1.Time + +
+ (Optional) +

The time after which the certificate stored in the secret named by this resource in spec.secretName is valid.

+
+ notAfter +
+ + Kubernetes meta/v1.Time + +
+ (Optional) +

The expiration time of the certificate stored in the secret named by this resource in spec.secretName.

+
+ renewalTime +
+ + Kubernetes meta/v1.Time + +
+ (Optional) +

RenewalTime is the time at which the certificate will be next renewed. If not set, no upcoming renewal is scheduled.

+
+ revision +
+ int +
+ (Optional) +

The current ‘revision’ of the certificate as issued.

+

+ When a CertificateRequest resource is created, it will have the + cert-manager.io/certificate-revision set to one greater than the current value of this field. +

+

Upon issuance, this field will be set to the value of the annotation on the CertificateRequest resource used to issue the certificate.

+

Persisting the value on the CertificateRequest resource allows the certificates controller to know whether a request is part of an old issuance or if it is part of the ongoing revision’s issuance by checking if the revision value in the annotation is greater than this field.

+
+ nextPrivateKeySecretName +
+ string +
+ (Optional) +

+ The name of the Secret resource containing the private key to be used for the next certificate iteration. The keymanager controller will automatically set this field if the + Issuing condition is set to True. It will automatically unset this field when the Issuing condition is not set or False. +

+
+ failedIssuanceAttempts +
+ int +
+ (Optional) +

The number of continuous failed issuance attempts up till now. This field gets removed (if set) on a successful issuance and gets set to 1 if unset and an issuance has failed. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1).

+
+

GenericIssuer

+
+

IssuerCondition

+

(Appears on: IssuerStatus)

+
+

IssuerCondition contains condition information for an Issuer.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ type +
+ + IssuerConditionType + +
+

Type of the condition, known values are (Ready).

+
+ status +
+ + ConditionStatus + +
+

Status of the condition, one of (True, False, Unknown).

+
+ lastTransitionTime +
+ + Kubernetes meta/v1.Time + +
+ (Optional) +

LastTransitionTime is the timestamp corresponding to the last status change of this condition.

+
+ reason +
+ string +
+ (Optional) +

Reason is a brief machine readable explanation for the condition’s last transition.

+
+ message +
+ string +
+ (Optional) +

Message is a human readable description of the details of the last transition, complementing reason.

+
+ observedGeneration +
+ int64 +
+ (Optional) +

If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer.

+
+

IssuerConditionType (string alias)

+

(Appears on: IssuerCondition)

+
+

IssuerConditionType represents an Issuer condition value.

+
+ + + + + + + + + + + + + +
ValueDescription
+

"Ready"

+
+

IssuerConditionReady represents the fact that a given Issuer condition is in ready state and able to issue certificates. If the status of this condition is False, CertificateRequest controllers should prevent attempts to sign certificates.

+
+

IssuerConfig

+

(Appears on: IssuerSpec)

+
+

The configuration for the issuer. Only one of these can be set.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ acme +
+ + ACMEIssuer + +
+ (Optional) +

ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates.

+
+ ca +
+ + CAIssuer + +
+ (Optional) +

CA configures this issuer to sign certificates using a signing CA keypair stored in a Secret resource. This is used to build internal PKIs that are managed by cert-manager.

+
+ vault +
+ + VaultIssuer + +
+ (Optional) +

Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend.

+
+ selfSigned +
+ + SelfSignedIssuer + +
+ (Optional) +

SelfSigned configures this issuer to ‘self sign’ certificates using the private key used to create the CertificateRequest object.

+
+ venafi +
+ + VenafiIssuer + +
+ (Optional) +

Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone.

+
+

IssuerSpec

+

(Appears on: ClusterIssuer, Issuer)

+
+

IssuerSpec is the specification of an Issuer. This includes any configuration required for the issuer.

+
+ + + + + + + + + + + + + +
FieldDescription
+ IssuerConfig +
+ + IssuerConfig + +
+

(Members of IssuerConfig are embedded into this type.)

+
+

IssuerStatus

+

(Appears on: ClusterIssuer, Issuer)

+
+

IssuerStatus contains status information about an Issuer

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ conditions +
+ + []IssuerCondition + +
+ (Optional) +

List of status conditions to indicate the status of a CertificateRequest. Known condition types are Ready.

+
+ acme +
+ + ACMEIssuerStatus + +
+ (Optional) +

ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates.

+
+

JKSKeystore

+

(Appears on: CertificateKeystores)

+
+

+ JKS configures options for storing a JKS keystore in the spec.secretName + Secret resource. +

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ create +
+ bool +
+

Create enables JKS keystore creation for the Certificate. If true, a file named keystore.jks will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. A file named truststore.jks will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef containing the issuing Certificate Authority

+
+ passwordSecretRef +
+ + SecretKeySelector + +
+

PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the JKS keystore.

+
+

KeyUsage (string alias)

+

(Appears on: CertificateRequestSpec, CertificateSpec)

+
+

+ KeyUsage specifies valid usage contexts for keys. See: + https://tools.ietf.org/html/rfc5280#section-4.2.1.3 + https://tools.ietf.org/html/rfc5280#section-4.2.1.12 +

+

Valid KeyUsage values are as follows: “signing”, “digital signature”, “content commitment”, “key encipherment”, “key agreement”, “data encipherment”, “cert sign”, “crl sign”, “encipher only”, “decipher only”, “any”, “server auth”, “client auth”, “code signing”, “email protection”, “s/mime”, “ipsec end system”, “ipsec tunnel”, “ipsec user”, “timestamping”, “ocsp signing”, “microsoft sgc”, “netscape sgc”

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueDescription
+

"any"

+
+

"crl sign"

+
+

"cert sign"

+
+

"client auth"

+
+

"code signing"

+
+

"content commitment"

+
+

"data encipherment"

+
+

"decipher only"

+
+

"digital signature"

+
+

"email protection"

+
+

"encipher only"

+
+

"ipsec end system"

+
+

"ipsec tunnel"

+
+

"ipsec user"

+
+

"key agreement"

+
+

"key encipherment"

+
+

"microsoft sgc"

+
+

"netscape sgc"

+
+

"ocsp signing"

+
+

"s/mime"

+
+

"server auth"

+
+

"signing"

+
+

"timestamping"

+
+

PKCS12Keystore

+

(Appears on: CertificateKeystores)

+
+

+ PKCS12 configures options for storing a PKCS12 keystore in the + spec.secretName Secret resource. +

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ create +
+ bool +
+

Create enables PKCS12 keystore creation for the Certificate. If true, a file named keystore.p12 will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. A file named truststore.p12 will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef containing the issuing Certificate Authority

+
+ passwordSecretRef +
+ + SecretKeySelector + +
+

PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the PKCS12 keystore.

+
+

PrivateKeyAlgorithm (string alias)

+

(Appears on: CertificatePrivateKey)

+
+ + + + + + + + + + + + + + + + + + + + + +
ValueDescription
+

"ECDSA"

+
+

Denotes the ECDSA private key type.

+
+

"Ed25519"

+
+

Denotes the Ed25519 private key type.

+
+

"RSA"

+
+

Denotes the RSA private key type.

+
+

PrivateKeyEncoding (string alias)

+

(Appears on: CertificatePrivateKey)

+
+ + + + + + + + + + + + + + + + + +
ValueDescription
+

"PKCS1"

+
+

PKCS1 key encoding will produce PEM files that include the type of private key as part of the PEM header, e.g. BEGIN RSA PRIVATE KEY. If the keyAlgorithm is set to ‘ECDSA’, this will produce private keys that use the BEGIN EC PRIVATE KEY header.

+
+

"PKCS8"

+
+

+ PKCS8 key encoding will produce PEM files with the BEGIN PRIVATE KEY + header. It encodes the keyAlgorithm of the private key as part of the DER encoded PEM block. +

+
+

PrivateKeyRotationPolicy (string alias)

+

(Appears on: CertificatePrivateKey)

+
+

Denotes how private keys should be generated or sourced when a Certificate is being issued.

+
+

SelfSignedIssuer

+

(Appears on: IssuerConfig)

+
+

Configures an issuer to ‘self sign’ certificates using the private key used to create the CertificateRequest object.

+
+ + + + + + + + + + + + + +
FieldDescription
+ crlDistributionPoints +
+ []string +
+ (Optional) +

The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings.

+
+

VaultAppRole

+

(Appears on: VaultAuth)

+
+

VaultAppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ path +
+ string +
+

Path where the App Role authentication backend is mounted in Vault, e.g: “approle”

+
+ roleId +
+ string +
+

RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault.

+
+ secretRef +
+ + SecretKeySelector + +
+

Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The key field must be specified and denotes which entry within the Secret resource is used as the app role secret.

+
+

VaultAuth

+

(Appears on: VaultIssuer)

+
+

Configuration used to authenticate with a Vault server. Only one of tokenSecretRef, appRole or kubernetes may be specified.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ tokenSecretRef +
+ + SecretKeySelector + +
+ (Optional) +

TokenSecretRef authenticates with Vault by presenting a token.

+
+ appRole +
+ + VaultAppRole + +
+ (Optional) +

AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource.

+
+ kubernetes +
+ + VaultKubernetesAuth + +
+ (Optional) +

Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server.

+
+

VaultIssuer

+

(Appears on: IssuerConfig)

+
+

Configures an issuer to sign certificates using a HashiCorp Vault PKI backend.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ auth +
+ + VaultAuth + +
+

Auth configures how cert-manager authenticates with the Vault server.

+
+ server +
+ string +
+

Server is the connection address for the Vault server, e.g: “https://vault.example.com:8200”.

+
+ path +
+ string +
+

Path is the mount path of the Vault PKI backend’s sign endpoint, e.g: “my_pki_mount/sign/my-role-name”.

+
+ namespace +
+ string +
+ (Optional) +

Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: “ns1” More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces

+
+ caBundle +
+ []byte +
+ (Optional) +

Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by Vault. Only used if using HTTPS to connect to Vault and ignored for HTTP connections. Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection.

+
+ caBundleSecretRef +
+ + SecretKeySelector + +
+ (Optional) +

Reference to a Secret containing a bundle of PEM-encoded CAs to use when verifying the certificate chain presented by Vault when using HTTPS. Mutually exclusive with CABundle. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. If no key for the Secret is specified, cert-manager will default to ‘ca.crt’.

+
+

VaultKubernetesAuth

+

(Appears on: VaultAuth)

+
+

Authenticate against Vault using a Kubernetes ServiceAccount token stored in a Secret.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ mountPath +
+ string +
+ (Optional) +

The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to /v1/auth/foo, will use the path /v1/auth/foo/login to authenticate with Vault. If unspecified, the default value “/v1/auth/kubernetes” will be used.

+
+ secretRef +
+ + SecretKeySelector + +
+

The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of ‘ambient credentials’ is not supported.

+
+ role +
+ string +
+

A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies.

+
+

VenafiCloud

+

(Appears on: VenafiIssuer)

+
+

VenafiCloud defines connection configuration details for Venafi Cloud

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ url +
+ string +
+ (Optional) +

URL is the base URL for Venafi Cloud. Defaults to “https://api.venafi.cloud/v1”.

+
+ apiTokenSecretRef +
+ + SecretKeySelector + +
+

APITokenSecretRef is a secret key selector for the Venafi Cloud API token.

+
+

VenafiIssuer

+

(Appears on: IssuerConfig)

+
+

Configures an issuer to sign certificates using a Venafi TPP or Cloud policy zone.

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ zone +
+ string +
+

Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required.

+
+ tpp +
+ + VenafiTPP + +
+ (Optional) +

TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified.

+
+ cloud +
+ + VenafiCloud + +
+ (Optional) +

Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified.

+
+

VenafiTPP

+

(Appears on: VenafiIssuer)

+
+

VenafiTPP defines connection configuration details for a Venafi TPP instance

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ url +
+ string +
+

URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: “https://tpp.example.com/vedsdk”.

+
+ credentialsRef +
+ + LocalObjectReference + +
+

CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, ‘username’ and ‘password’.

+
+ caBundle +
+ []byte +
+ (Optional) +

Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by the TPP server. Only used if using HTTPS; ignored for HTTP. If undefined, the certificate bundle in the cert-manager controller container is used to validate the chain.

+
+

X509Subject

+

(Appears on: CertificateSpec)

+
+

X509Subject Full X509 name specification

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+ organizations +
+ []string +
+ (Optional) +

Organizations to be used on the Certificate.

+
+ countries +
+ []string +
+ (Optional) +

Countries to be used on the Certificate.

+
+ organizationalUnits +
+ []string +
+ (Optional) +

Organizational Units to be used on the Certificate.

+
+ localities +
+ []string +
+ (Optional) +

Cities to be used on the Certificate.

+
+ provinces +
+ []string +
+ (Optional) +

State/Provinces to be used on the Certificate.

+
+ streetAddresses +
+ []string +
+ (Optional) +

Street addresses to be used on the Certificate.

+
+ postalCodes +
+ []string +
+ (Optional) +

Postal codes to be used on the Certificate.

+
+ serialNumber +
+ string +
+ (Optional) +

Serial number to be used on the Certificate.

+
+
+

meta.cert-manager.io/v1

+
+

Package v1 contains meta types for cert-manager APIs

+
+

Resource Types:

+
    +

    ConditionStatus (string alias)

    +

    (Appears on: CertificateCondition, CertificateRequestCondition, IssuerCondition)

    +
    +

    ConditionStatus represents a condition’s status.

    +
    + + + + + + + + + + + + + + + + + + + + + +
    ValueDescription
    +

    "False"

    +
    +

    ConditionFalse represents the fact that a given condition is false

    +
    +

    "True"

    +
    +

    ConditionTrue represents the fact that a given condition is true

    +
    +

    "Unknown"

    +
    +

    ConditionUnknown represents the fact that a given condition is unknown

    +
    +

    LocalObjectReference

    +

    (Appears on: VenafiTPP, SecretKeySelector)

    +
    +

    A reference to an object in the same namespace as the referent. If the referent is a cluster-scoped resource (e.g. a ClusterIssuer), the reference instead refers to the resource with the given name in the configured ‘cluster resource namespace’, which is set as a flag on the controller component (and defaults to the namespace that cert-manager runs in).

    +
    + + + + + + + + + + + + + +
    FieldDescription
    + name +
    + string +
    +

    Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

    +
    +

    ObjectReference

    +

    (Appears on: ChallengeSpec, OrderSpec, CertificateRequestSpec, CertificateSpec)

    +
    +

    ObjectReference is a reference to an object with a given name, kind and group.

    +
    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    + name +
    + string +
    +

    Name of the resource being referred to.

    +
    + kind +
    + string +
    + (Optional) +

    Kind of the resource being referred to.

    +
    + group +
    + string +
    + (Optional) +

    Group of the resource being referred to.

    +
    +

    SecretKeySelector

    +

    + (Appears on: ACMEExternalAccountBinding, ACMEIssuer, ACMEIssuerDNS01ProviderAcmeDNS, ACMEIssuerDNS01ProviderAkamai, ACMEIssuerDNS01ProviderAzureDNS, ACMEIssuerDNS01ProviderCloudDNS, ACMEIssuerDNS01ProviderCloudflare, ACMEIssuerDNS01ProviderDigitalOcean, ACMEIssuerDNS01ProviderRFC2136, + ACMEIssuerDNS01ProviderRoute53, JKSKeystore, PKCS12Keystore, VaultAppRole, VaultAuth, VaultIssuer, VaultKubernetesAuth, VenafiCloud) +

    +
    +

    A reference to a specific ‘key’ within a Secret resource. In some instances, key is a required field.

    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    + LocalObjectReference +
    + + LocalObjectReference + +
    +

    (Members of LocalObjectReference are embedded into this type.)

    +

    The name of the Secret resource being referred to.

    +
    + key +
    + string +
    + (Optional) +

    The key of the entry in the Secret resource’s data field to be used. Some instances of this field may be defaulted, in others it may be required.

    +
    +
    +

    webhook.config.cert-manager.io/v1alpha1

    +
    +

    Package v1alpha1 is the v1alpha1 version of the webhook config API.

    +
    +

    Resource Types:

    +
      +

      DynamicServingConfig

      +

      (Appears on: TLSConfig)

      +
      +

      DynamicServingConfig makes the webhook generate a CA and persist it into Secret resources. This CA will be used by all instances of the webhook for signing serving certificates.

      +
      + + + + + + + + + + + + + + + + + + + + + +
      FieldDescription
      + secretNamespace +
      + string +
      +

      Namespace of the Kubernetes Secret resource containing the TLS certificate used as a CA to sign dynamic serving certificates.

      +
      + secretName +
      + string +
      +

      Namespace of the Kubernetes Secret resource containing the TLS certificate used as a CA to sign dynamic serving certificates.

      +
      + dnsNames +
      + []string +
      +

      DNSNames that must be present on serving certificates signed by the CA.

      +
      +

      FilesystemServingConfig

      +

      (Appears on: TLSConfig)

      +
      +

      FilesystemServingConfig enables using a certificate and private key found on the local filesystem. These files will be periodically polled in case they have changed, and dynamically reloaded.

      +
      + + + + + + + + + + + + + + + + + +
      FieldDescription
      + certFile +
      + string +
      +

      Path to a file containing TLS certificate & chain to serve with

      +
      + keyFile +
      + string +
      +

      Path to a file containing a TLS private key to server with

      +
      +

      TLSConfig

      +

      (Appears on: WebhookConfiguration)

      +
      +

      TLSConfig configures how TLS certificates are sourced for serving. Only one of ‘filesystem’ or ‘dynamic’ may be specified.

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      FieldDescription
      + cipherSuites +
      + []string +
      +

      cipherSuites is the list of allowed cipher suites for the server. Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). If not specified, the default for the Go version will be used and may change over time.

      +
      + minTLSVersion +
      + string +
      +

      minTLSVersion is the minimum TLS version supported. Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). If not specified, the default for the Go version will be used and may change over time.

      +
      + filesystem +
      + + FilesystemServingConfig + +
      +

      Filesystem enables using a certificate and private key found on the local filesystem. These files will be periodically polled in case they have changed, and dynamically reloaded.

      +
      + dynamic +
      + + DynamicServingConfig + +
      +

      When Dynamic serving is enabled, the webhook will generate a CA used to sign webhook certificates and persist it into a Kubernetes Secret resource (for other replicas of the webhook to consume). It will then generate a certificate in-memory for itself using this CA to serve with. The CAs certificate can then be copied into the appropriate Validating, Mutating and Conversion webhook configuration objects (typically by cainjector).

      +
      +

      WebhookConfiguration

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      FieldDescription
      + securePort +
      + int +
      +

      securePort is the port number to listen on for secure TLS connections from the kube-apiserver. Defaults to 6443.

      +
      + healthzPort +
      + int +
      +

      healthzPort is the port number to listen on (using plaintext HTTP) for healthz connections. Defaults to 6080.

      +
      + tlsConfig +
      + + TLSConfig + +
      +

      tlsConfig is used to configure the secure listener’s TLS settings.

      +
      + kubeConfig +
      + string +
      +

      kubeConfig is the kubeconfig file used to connect to the Kubernetes apiserver. If not specified, the webhook will attempt to load the in-cluster-config.

      +
      + apiServerHost +
      + string +
      +

      apiServerHost is used to override the API server connection address. Deprecated: use kubeConfig instead.

      +
      + enablePprof +
      + bool +
      +

      enablePprof configures whether pprof is enabled.

      +
      + pprofAddress +
      + string +
      +

      pprofAddress configures the address on which /debug/pprof endpoint will be served if enabled. Defaults to ‘localhost:6060’.

      +
      + featureGates +
      + map[string]bool +
      + (Optional) +

      featureGates is a map of feature names to bools that enable or disable experimental features. Default: nil

      +
      +
      +

      + Generated with gen-crd-api-reference-docs on git commit 7ebb5f515. +

      diff --git a/content/v1.12-docs/reference/cmctl.md b/content/v1.12-docs/reference/cmctl.md new file mode 100644 index 0000000000..095c9dfe6e --- /dev/null +++ b/content/v1.12-docs/reference/cmctl.md @@ -0,0 +1,344 @@ +--- +title: The cert-manager Command Line Tool (cmctl) +description: | + cmctl is a command line tool that can help you manage cert-manager and its resources inside your cluster +--- + +`cmctl` is a command line tool that can help you manage cert-manager and its resources inside your cluster. + +## Installation + +### Homebrew + +On Mac or Linux if you have [Homebrew](https://brew.sh) installed, you can +install `cmctl` with: + +```console +brew install cmctl +``` + +This will also install shell completion. + +### Manual Installation + +You need the `cmctl.tar.gz` file for the platform you're using, these can be +found on our +[GitHub releases page](https://github.com/cert-manager/cert-manager/releases). +In order to use `cmctl` you need its binary to be accessible under +the name `cmctl` in your `$PATH`. +Run the following commands to set up the CLI. Replace OS and ARCH with your +systems equivalents: + +```console +OS=$(go env GOOS); ARCH=$(go env GOARCH); curl -fsSL -o cmctl.tar.gz https://github.com/cert-manager/cert-manager/releases/latest/download/cmctl-$OS-$ARCH.tar.gz +tar xzf cmctl.tar.gz +sudo mv cmctl /usr/local/bin +``` + +You can run `cmctl help` to test the CLI is set up properly: + +```console +$ cmctl help + +cmctl is a CLI tool manage and configure cert-manager resources for Kubernetes + +Usage: cmctl [command] + +Available Commands: + approve Approve a CertificateRequest + check Check cert-manager components + completion Generate completion scripts for the cert-manager CLI + convert Convert cert-manager config files between different API versions + create Create cert-manager resources + deny Deny a CertificateRequest + experimental Interact with experimental features + help Help about any command + inspect Get details on certificate related resources + renew Mark a Certificate for manual renewal + status Get details on current status of cert-manager resources + upgrade Tools that assist in upgrading cert-manager + version Print the cert-manager CLI version and the deployed cert-manager version + +Flags: + -h, --help help for cmctl + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + +Use "cmctl [command] --help" for more information about a command. +``` + +> There is also a [legacy kubectl plugin](#legacy-kubectl-plugin), but it is no longer recommended +> because the standalone `cmctl` binary provides better [auto-completion](#completion). + +## Commands + +### Approve and Deny CertificateRequests + +CertificateRequests can be +[approved or denied](../concepts/certificaterequest.md#approval) using their +respective cmctl commands: + +> **Note**: The internal cert-manager approver may automatically approve all +> CertificateRequests unless disabled with the flag on the cert-manager-controller +> `--controllers=*,-certificaterequests-approver` + +```bash +$ cmctl approve -n istio-system mesh-ca --reason "pki-team" --message "this certificate is valid" +Approved CertificateRequest 'istio-system/mesh-ca' +``` + +```bash +$ cmctl deny -n my-app my-app --reason "example.com" --message "violates policy" +Denied CertificateRequest 'my-app/my-app' +``` + +### Convert + +`cmctl convert` can be used to convert cert-manager manifest files between +different API versions. Both YAML and JSON formats are accepted. The command +either takes a file name, directory path, or a URL as input. The contents is +converted into the format of the latest API version known to cert-manager, or +the one specified by `--output-version` flag. + +The default output will be printed to stdout in YAML format. One can use the +option `-o` to change the output destination. + +For example, this will output `cert.yaml` in the latest API version: + +```console +cmctl convert -f cert.yaml +``` + +### Create + +`cmctl create` can be used to create cert-manager resources manually. +Sub-commands are available to create different resources: + +#### CertificateRequest + +To create a cert-manager CertificateRequest, use `cmctl create +certificaterequest`. The command takes in the name of the CertificateRequest to +be created, and creates a new CertificateRequest resource based on the YAML +manifest of a Certificate resource as specified by `--from-certificate-file` +flag, by generating a private key locally and creating a 'certificate signing +request' to be submitted to a cert-manager Issuer. The private key will be +written to a local file, where the default is `.key`, or it can be +specified using the `--output-key-file` flag. + +If you wish to wait for the CertificateRequest to be signed and store the X.509 +certificate in a file, you can set the `--fetch-certificate` flag. The default +timeout when waiting for the issuance of the certificate is 5 minutes, but can +be specified with the `--timeout` flag. The default name of the file storing the +X.509 certificate is `.crt`, you can use the ` +--output-certificate-file` flag to specify otherwise. + +Note that the private key and the X.509 certificate are both written to file, +and are **not** stored inside Kubernetes. + +For example this will create a CertificateRequest resource with the name "my-cr" +based on the cert-manager Certificate described in `my-certificate.yaml` while +storing the private key and X.509 certificate in `my-cr.key` and `my-cr.crt` +respectively. + +```console +cmctl create certificaterequest my-cr --from-certificate-file my-certificate.yaml --fetch-certificate --timeout 20m +``` + +### Renew + +`cmctl` allows you to manually trigger a renewal of a specific certificate. +This can be done either one certificate at a time, using label selectors (`-l app=example`), or with the `--all` flag: + +For example, you can renew the certificate `example-com-tls`: +```console +$ kubectl get certificate +NAME READY SECRET AGE +example-com-tls True example-com-tls 1d + +$ cmctl renew example-com-tls +Manually triggered issuance of Certificate default/example-com-tls + +$ kubectl get certificaterequest +NAME READY AGE +example-com-tls-tls-8rbv2 False 10s +``` + +You can also renew all certificates in a given namespace: + +```console +$ cmctl renew --namespace=app --all +``` + +The renew command allows several options to be specified: +* `--all` renew all Certificates in the given Namespace, or all namespaces when combined with `--all-namespaces` +* `-A` or `--all-namespaces` mark Certificates across namespaces for renewal +* `-l` `--selector` allows set a label query to filter on +as well as `kubectl` like global flags like `--context` and `--namespace`. + +### Status Certificate + +`cmctl status certificate` outputs the details of the current status of a +Certificate resource and related resources like CertificateRequest, Secret, +Issuer, as well as Order and Challenges if it is a ACME Certificate. The +command outputs information about the resources, including Conditions, Events +and resource specific fields like Key Usages and Extended Key Usages of the +Secret or Authorizations of the Order. This will be helpful for troubleshooting +a Certificate. + +The command takes in one argument specifying the name of the Certificate +resource and the namespace can be specified as usual with the `-n` or +`--namespace` flag. + +This example queries the status of the Certificate named `my-certificate` in +namespace `my-namespace`. + +```console +cmctl status certificate my-certificate -n my-namespace +``` + +### Completion + +`cmctl` supports auto-completion for both subcommands as well as suggestions for +runtime objects. + +```console +$ cmctl approve -n +default kube-node-lease kube-public kube-system local-path-storage +``` + +Completion can be installed for your environment by following the instructions +for the shell you are using. It currently supports bash, fish, zsh, and +powershell. + +```console +$ cmctl completion help +``` + +--- + +### Experimental +`cmctl x` has experimental sub-commands for operations which are currently under +evaluation to be included into cert-manager proper. The behavior and interface +of these commands are subject to change or removal in future releases. + + +#### Create +`cmctl x create` can be used to create cert-manager resources manually. +Sub-commands are available to create different resources: + +##### CertificateSigningRequest +To create a [CertificateSigningRequest](../usage/kube-csr.md), use +```console +cmctl x create csr +``` +This command takes the name of the CertificateSigningRequest to be created, as +well as a file containing a Certificate manifest (`-f, +--from-certificate-file`). This command will generate a private key, based on +the options of the Certificate, and write it to the local file `.key`, or +specified by `-k, --output-key-file`. + +```bash +$ cmctl x create csr -f my-cert.yaml my-req +``` + + +
      + +cert-manager **will not** automatically approve CertificateSigningRequests. If +you are not running a custom approver in your cluster, you will likely need to +manually approve the CertificateSigningRequest: + +```bash +$ kubectl certificate approve +``` + +
      + +This command can also wait for the CertificateSigningRequest to be signed using +the flag `-w, --fetch-certificate`. Once signed it will write the resulting +signed certificate to the local file `.crt`, or specified by `-c, +--output-certificate-file`. + +```bash +$ cmctl x create csr -f my-cert.yaml my-req -w +``` + +#### Install + +```bash +cmctl x install +``` + +This command makes sure that the required `CustomResourceDefinitions` are installed together with the cert-manager, cainjector and webhook components. +Under the hood, a procedure similar to the [Helm install procedure](../installation/helm.md#steps) is used. + +You can also use `cmctl x install` to customize the installation of cert-manager. + +The example below shows how to tune the cert-manager installation by overriding the default Helm values: + +```bash +cmctl x install \ + --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter + --set webhook.timeoutSeconds=4s # Example: changing the wehbook timeout using a Helm parameter +``` + +You can find [a full list of the install parameters on cert-manager's ArtifactHub page](https://artifacthub.io/packages/helm/cert-manager/cert-manager#configuration). These are the same parameters that are available when using the Helm chart. +Once you have deployed cert-manager, you can [verify](../installation/verify.md) the installation. + +The CLI also allows the user to output the templated manifest to `stdout`, instead of installing the manifest on the cluster. + +```bash +cmctl x install --dry-run > cert-manager.custom.yaml +``` + +#### Uninstall + +```bash +cmctl x uninstall +``` + +This command uninstalls any Helm-managed release of cert-manager. + +The CRDs will be deleted if you installed cert-manager with the option `--set CRDs=true`. + +Most of the features supported by `helm uninstall` are also supported by this command. + +Some example uses: + +```bash +cmctl x uninstall + +cmctl x uninstall --namespace my-cert-manager + +cmctl x uninstall --dry-run + +cmctl x uninstall --no-hooks +``` + +### Upgrade + +Tools that assist in upgrading cert-manager + +```bash +$ cmctl upgrade --help +``` +##### Migrate API version + +This command can be used to prepare a cert-manager installation that was created +before cert-manager `v1` for upgrading to a cert-manager version `v1.6` or later. +It ensures that any cert-manager custom resources that may have been stored in etcd at +a deprecated API version get migrated to `v1`. See [Migrating Deprecated API +Resources](https://cert-manager.io/docs/installation/upgrading/remove-deprecated-apis) for more context. + +```bash +$ cmctl upgrade migrate-api-version --qps 5 --burst 10 +``` + +## Legacy kubectl plugin + +While the kubectl plugin is supported, it is recommended to use `cmctl` as this enables a better experience via tab auto-completion. + +To install the plugin you need the `kubectl-cert-manager.tar.gz` file for the platform you're using, +these can be found on our [GitHub releases page](https://github.com/cert-manager/cert-manager/releases). +In order to use the kubectl plugin you need its binary to be accessible under the name `kubectl-cert_manager` in your `$PATH`. + +You can run `kubectl cert-manager help` to test that the plugin is set up properly. diff --git a/content/v1.12-docs/reference/tls-terminology.md b/content/v1.12-docs/reference/tls-terminology.md new file mode 100644 index 0000000000..23b6df3447 --- /dev/null +++ b/content/v1.12-docs/reference/tls-terminology.md @@ -0,0 +1,79 @@ +--- +title: TLS Terminology +description: | + Learn about the TLS terminology used in the cert-manager documentation such as publicly trusted, self-signed, root, intermediate and leaf certificate +--- + +Learn about the TLS terminology used in the cert-manager documentation such as `publicly trusted`, `self-signed`, `root`, `intermediate` and `leaf` _certificate_. + +## Overview + +With TLS being so widely deployed, terminology can sometimes get confused or be used to mean different things, and that reality +combined with the complexity of TLS can lead to serious misunderstandings and confusion. + +For further reference, you might want to check out some relevant RFCs: + +- [RFC 5246: TLS 1.2](https://datatracker.ietf.org/doc/html/rfc5246) +- [RFC 8446: TLS 1.3](https://datatracker.ietf.org/doc/html/rfc8446) +- [RFC 5280: X.509](https://datatracker.ietf.org/doc/html/rfc5280) + +## Definitions + +### `publicly trusted` + +What does "publicly trusted" mean? + +Broadly speaking, a "publicly trusted" certificate is one that you can use on the Internet and expect +that most reasonably up-to-date computers will be able to verify it using their system trust store. + +There isn't a single standard trust store containing certs which are "publicly trusted", but generally most +of the commonly seen trust stores are similar. An example would be [Mozilla's CA Certificate Program](https://wiki.mozilla.org/CA). + +### What does "self-signed" mean? Is my CA self-signed? + +Self-signed means exactly what it says; a certificate is self-signed if it is signed by its own private key. + +Self-signed is a commonly confused term, however, and is very frequently misused to mean "not publicly trusted". We tend to use terms +like "private PKI" to denote the situation where an organization might have their own internal CA certificates which wouldn't +be trusted outside of the organization. + +As an example, there are _many_ self-signed certificates in [Mozilla's CA Certificate Program](https://wiki.mozilla.org/CA), but +all of those certificates would usually be described as "publicly trusted". + +Your certificate is self-signed only if it's signed with its own key. + +### What's the difference between "root", "intermediate", and "leaf" certificates? + +cert-manager uses the following definitions: + +#### Root Certificates + +Roots are self-signed certificates and almost always marked as CA certificates. They're usually not sent over the wire +during a TLS handshake because they need to be explicitly trusted in order to be validated. + +Roots are sometimes defined as "CA certificates which are explicitly trusted"---which can include certificates which +aren't self-signed. cert-manager doesn't use this definition. + +Changing trust stores to include new roots or remove old ones is a non-trivial task which can take months or years for publicly +trusted roots. For this reason roots are usually issued with very long lifetimes, often on the order of decades. + +#### Intermediate Certificates + +Intermediates are CA certificates signed by another CA. Most intermediates will be signed by a root certificate, but it's +possible to construct longer chains where an intermediate can be signed by another intermediate. + +Intermediate certificates are usually issued with a much shorter lifetime than the CA which signed them. On the +Internet, intermediate certificates are used on network-connected machines for day-to-day issuance so that the +highly-valuable root certificates can remain entirely offline. + +While intermediate certificates can also be explicitly trusted via addition to a trust store, they're usually validated +by "walking up" the chain and validating signatures until an explicitly trusted self-signed root certificate is found. + +#### Leaf Certificates + +Leaf certificates are usually used to represent a particular identity, rather than being used to sign other certificates. +On the Internet leaf certificates usually identify a particular domain, such as `example.com`. + +Leaf certificates are sent first in a chain of certificates and represent the end of that chain. They must be sent +along with any intermediates required to create a chain which can be validated by verifying signatures up to a trusted +root certificate. diff --git a/content/v1.12-docs/troubleshooting/README.md b/content/v1.12-docs/troubleshooting/README.md new file mode 100644 index 0000000000..fff96f1672 --- /dev/null +++ b/content/v1.12-docs/troubleshooting/README.md @@ -0,0 +1,116 @@ +--- +title: Troubleshooting +description: | + Learn how to debug common problems with cert-manager +--- + +In this section, you will learn troubleshooting techniques that will help you find the root cause if your Certificate fails to be issued or renewed. + +This section also includes the following guides: + +* [Troubleshooting Problems with ACME / Let's Encrypt Certificates](./acme.md): + Learn more about how the ACME issuer works and how to diagnose problems with it. +* [Troubleshooting Problems with the Webhook](./webhook.md): + Learn how to diagnose problems with the cert-manager webhook. + +## Overview + +When troubleshooting cert-manager your best friend is `kubectl describe`, this will give you information on the resources as well as recent events. It is not advised to use the logs as these are quite verbose and only should be looked at if the following steps do not provide help. + +cert-manager consists of multiple custom resources that live inside your Kubernetes cluster, these resources are linked together and are often created by one another. When such an event happens it will be reflected in a Kubernetes event, you can see these per-namespace using `kubectl get event`, or in the output of `kubectl describe` when looking at a single resource. + +## Troubleshooting a failed certificate request + +There are several resources that are involved in requesting a certificate. + +``` + + ( +---------+ ) + ( | Ingress | ) Optional ACME Only! + ( +---------+ ) + | | + | +-------------+ +--------------------+ | +-------+ +-----------+ + |-> | Certificate |----> | CertificateRequest | ----> | | Order | ----> | Challenge | + +-------------+ +--------------------+ | +-------+ +-----------+ + | +``` + +The cert-manager flow all starts at a `Certificate` resource, you can create this yourself or your Ingress resource will do this for you if you have the [correct annotations](../usage/ingress.md) set. + +### 1. Checking the Certificate resource +First we have to check if we have a `Certificate` resource created in our namespace. We can get these using `kubectl get certificate`. +```console +$ kubectl get certificate +NAME READY AGE +example-com-tls False 1h +``` + +If none is present and you plan to use the [ingress-shim](../usage/ingress.md): check the ingress annotations more about that is in the [ingress troubleshooting guide](../usage/ingress.md#troubleshooting). +If you are not using the ingress-shim: check the output of the command you used to create the certificate. + +If you see one with ready status `False` you can get more info using `kubectl describe certificate`, if the status is `True` that means that cert-manager has successfully issued a certificate. +```console +$ kubectl describe certificate +[...] +Status: + Conditions: + Last Transition Time: 2020-05-15T21:45:22Z + Message: Issuing certificate as Secret does not exist + Reason: DoesNotExist + Status: False + Type: Ready + Next Private Key Secret Name: example-tls-wtlww +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Issuing 105s cert-manager Issuing certificate as Secret does not exist + Normal Generated 105s cert-manager Stored new private key in temporary Secret resource "example-tls-wtlww" + Normal Requested 104s cert-manager Created new CertificateRequest resource "example-tls-bw5t9" +``` + +Here you will find more info about the current certificate status under `Status` as well as detailed information about what happened to it under `Events`. Both will help you determine the current state of the certificate. +The last status is `Created new CertificateRequest resource`, it is worth taking a look at in which state `CertificateRequest` is to get more info on why our `Certificate` isn't getting issued. + +### 2. Checking the `CertificateRequest` +The `CertificateRequest` resource represents a CSR in cert-manager and passes this CSR on onto the issuer. +You can find the name of the `CertificateRequest` in the `Certificate` event log or using `kubectl get certificaterequest` + +To get more info we again run `kubectl describe`: +```console +$ kubectl describe certificaterequest +API Version: cert-manager.io/v1 +Kind: CertificateRequest +Spec: + Request: [...] + Issuer Ref: + Group: cert-manager.io + Kind: ClusterIssuer + Name: letencrypt-production +Status: + Conditions: + Last Transition Time: 2020-05-15T21:45:36Z + Message: Waiting on certificate issuance from order example-tls-fqtfg-1165244518: "pending" + Reason: Pending + Status: False + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal OrderCreated 8m20s cert-manager Created Order resource example-tls-fqtfg-1165244518 +``` + +Here we will see any issue regarding the Issuer configuration as well as Issuer responses. + +### 3. Check the issuer state +If in the above steps you saw an issuer not ready error you can do the same steps again for (cluster)issuer resources: +```console +$ kubectl describe issuer +$ kubectl describe clusterissuer +``` + +These will allow you to get any error messages regarding accounts or network issues with your issuer. +Troubleshooting ACME issuers is described in more detail in [Troubleshooting Issuing ACME Certificates](./acme.md). + +### 4. ACME Troubleshooting +ACME (e.g. Let's Encrypt) issuers have 2 additional resources inside cert-manager: `Orders` and `Challenges`. +Troubleshooting these is described in [Troubleshooting Issuing ACME Certificates](./acme.md). diff --git a/content/v1.12-docs/troubleshooting/acme.md b/content/v1.12-docs/troubleshooting/acme.md new file mode 100644 index 0000000000..c8b527b1cf --- /dev/null +++ b/content/v1.12-docs/troubleshooting/acme.md @@ -0,0 +1,226 @@ +--- +title: Troubleshooting Problems with ACME / Let's Encrypt Certificates +description: | + Learn how to diagnose problems if cert-manager fails to renew ACME / Let's Encrypt Certificates. +--- + +Learn how to diagnose problems if cert-manager fails to renew ACME / Let's Encrypt Certificates. + +## Overview + +When requesting ACME certificates, cert-manager will create `Order` and +`Challenges` to complete the request. As such, there are more resources to +investigate and debug if there is a problem during the process. You can read +more about these resources in the [concepts +pages](../concepts/acme-orders-challenges.md). + +Before you start here you should probably take a look at our [general troubleshooting guide](./README.md) + +## 1. Troubleshooting (Cluster)Issuers + +First of all check if the (Cluster)Issuer you're using is in a ready state: +```bash +$ kubectl get issuer +$ kubectl get clusterissuer +NAME READY AGE +letsencrypt True 38m +letsencrypt-http False 32m +``` + +If you see `False` check the status using `kubectl describe`. For example: +```bash +$ kubectl describe issuer letsencrypt-http +$ kubectl describe clusterissuer letsencrypt-http +Name: letsencrypt +API Version: cert-manager.io/v1 +Kind: Issuer +Spec: + Acme: + Email: cert-manager@example.com + Private Key Secret Ref: + Name: letsencrypt + Server: https://acme-staging-v02.api.letsencrypt.org/directory +Status: + Acme: + Conditions: + Message: Failed to update ACME account:400 urn:ietf:params:acme:error:invalidEmail: Unable to update account :: invalid contact domain. Contact emails @example.com are forbidden + Reason: ErrUpdateACMEAccount + Status: False + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Warning ErrUpdateACMEAccount 101s (x3 over 106s) cert-manager Failed to update ACME account:400 urn:ietf:params:acme:error:invalidEmail: Unable to update account :: invalid contact domain. Contact emails @example.com are forbidden +``` + +### Common errors + +* `Failed to update ACME account:400 urn:ietf:params:acme:error:invalidEmail`: the email you specified in the Issuer configuration isn't valid. +* `Error initializing issuer: Failed to register ACME account: secrets "acme-key" already exists`: there might be a leftover account from a previous issuer that is no longer valid, you should remove the secret so it can be recreated. +* `Error accepting challenge: 400 urn:ietf:params:acme:error:malformed: Unable to update challenge :: authorization must be pending`: this suggests that the authorization was not in 'pending' state at a time when cert-manager sent a request to the ACME server to accept the challenge. This may be because the domain validation has already failed and the authorization has been marked as 'invalid'. Check the authorization URL on the status of the `Order` or `Challenge` to see the status of the authorization and any additional information. + +## 2. Troubleshooting Orders + +When we run a describe on the `CertificateRequest` resource we see that an `Order` that has +been created: + +```bash +$ kubectl describe certificaterequest example-com-2745722290 +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal OrderCreated 5s cert-manager Created Order resource default/example-com-2745722290-439160286 +``` + +Orders are a request to an ACME instance to issue a certificate. +By running `kubectl describe order` on a particular order, +information can be gleaned about failures in the process: + +```console +$ kubectl describe order example-com-2745722290-439160286 +... +Reason: +State: pending +URL: https://acme-v02.api.letsencrypt.org/acme/order/41123272/265506123 +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Created 1m cert-manager Created Challenge resource "example-com-2745722290-439160286-0" for domain "test1.example.com" + Normal Created 1m cert-manager Created Challenge resource "example-com-2745722290-439160286-1" for domain "test2.example.com" +``` + +Here we can see that cert-manager has created two Challenge resources to verify we control specific domains, +a requirements of the ACME order to obtain a signed certificate. + +You can then go on to run +`kubectl describe challenge example-com-2745722290-439160286-0` to further debug the +progress of the Order. + +Once an Order is successful, you should see an event like the following: + +```bash +$ kubectl describe order example-com-2745722290-439160286 +... +Reason: +State: valid +URL: https://acme-v02.api.letsencrypt.org/acme/order/41123272/265506123 +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Created 72s cert-manager Created Challenge resource "example-com-2745722290-439160286-0" for domain "test1.example.com" + Normal Created 72s cert-manager Created Challenge resource "example-com-2745722290-439160286-1" for domain "test2.example.com" + Normal OrderValid 4s cert-manager Order completed successfully +``` + +You can see some additional information about the state of the [ACME authorization](https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.4) that needs to be validated as part of this order using the authorization URL from the status of the `Order`: + +```bash +$ kubectl get order -ojsonpath='{.status.authorizations[x].url}' +``` + +If the Order is not completing successfully, you can debug the challenges +for the Order by running `kubectl describe` on the `Challenge` resource which is described in the following steps. + +## 3. Troubleshooting Challenges + +In order to determine why an ACME Order is not being finished, we can debug +using the `Challenge` resources that cert-manager has created. + +In order to determine which `Challenge` is failing, you can run +`kubectl get challenges`: + + +```console +$ kubectl get challenges +... +NAME STATE DOMAIN REASON AGE +example-com-2745722290-4391602865-0 pending example.com Waiting for dns-01 challenge propagation 22s +``` + +This shows that the challenge has been presented using the DNS01 solver +successfully and now cert-manager is waiting for the 'self check' to pass. + +You can get more information about the challenge and it's lifecycle by using `kubectl describe`: + +```bash +$ kubectl describe challenge example-com-2745722290-4391602865-0 +... +Status: + Presented: true + Processing: true + Reason: Waiting for dns-01 challenge propagation + State: pending +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Started 19s cert-manager Challenge scheduled for processing + Normal Presented 16s cert-manager Presented challenge using dns-01 challenge mechanism +``` + +Progress about the state of each challenge will be recorded either as Events +or on the Challenge's `status` block (as shown above). + +In case of DNS01 you will find any errors from your DNS provider here. + +Both HTTP01 and DNS01 go through a "self-check" first before cert-manager presents the challenge to the ACME provider. +This is done not to overload the ACME provider with failed challenges due to DNS or loadbalancer propagations. +The status of this can be found in the Status block of the describe: +```console +$ kubectl describe challenge +[...] +Status: + Presented: true + Processing: true + Reason: Waiting for http-01 challenge propagation: failed to perform self check GET request 'http://example.com/.well-known/acme-challenge/_fgdLz0i3TFiZW4LBjuhjgd5nTOkaMBhxYmTY': Get "http://example.com/.well-known/acme-challenge/_fgdLz0i3TFiZW4LBjuhjgd5nTOkaMBhxYmTY: remote error: tls: handshake failure + State: pending +[...] +``` + +In this example our HTTP01 check fails due a network issue. +You will also see any errors coming from your DNS provider here. + +You can also see some additional information about the state of the [ACME authorization](https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.4) that the challenge should validate using the authorization URL on from the status of the `Challenge`: + +```bash +$ kubectl get challenge -ojsonpath='{.spec.authorizationURL}' +``` + +### HTTP01 troubleshooting +First of all check if you can see the challenge URL from the public internet, if this does not work check your Ingress and firewall configuration as well as the service and pod cert-manager created to solve the ACME challenge. +If this does work check if your cluster can see it too. It is important to test this from inside a Pod. If you get a connection error it is suggested to check the cluster's network configuration. +If you receive a `tls: handshake failure`, try setting the annotation `cert-manager.io/issue-temporary-certificate: "true"` on the Ingress or Certificate resource. This will issue a temporary self signed certificate for the ingress controller to use before the actual certificate is issued. +If you still are having issues, there may be an issue with your ingress controller handling multiple resources for the same hostname, in this case, the annotation `acme.cert-manager.io/http01-edit-in-place: "true"` is likely required. + +For example when using GKE with the Google Cloud Loadbalancer it is recommended to set: +``` +cert-manager.io/issue-temporary-certificate: "true" +acme.cert-manager.io/http01-edit-in-place: "true" +``` +This will allow the Google Cloud Loadbalancer to propagate a HTTPS endpoint correctly with a temporary certificate, the `http01-edit-in-place` part will prevent GKE from assigning a 2nd IP address for the challenge endpoint. + +#### Got 404 status code +If your challenge self-check fails with a 404 not found error. Make sure to check the following: + +* you can access the URL from the public internet +* the ACME solver pod is up and running +* use `kubectl describe ingress` to check the status of the HTTP01 solver ingress. (unless you use `acme.cert-manager.io/http01-edit-in-place`, then check the same ingress as your domain) + +### DNS01 troubleshooting +If you see no error events about your DNS provider you can check the following +Check if you can see the `_acme_challenge.domain` TXT DNS record from the public internet, or in your DNS provider's interface. +cert-manager will check if a DNS record has been propagated by querying the cluster's DNS solver. If you are able to see it from the public internet but not from inside the cluster you might want to change [the DNS server for self-check](../configuration/acme/dns01/README.md#setting-nameservers-for-dns01-self-check) as some cloud providers overwrite DNS internally. + +#### cert-manager identifies the wrong zone for your domain name +cert-manager by default uses SOA (Start of Authority) records to determine which zone name to use at your DNS provider. +Some DNS resolvers will filter this information, if this is the case cert-manager cannot determine the zone and it is advised to [change the DNS server for DNS01 self-checks](../configuration/acme/dns01/README.md#setting-nameservers-for-dns01-self-check). + +If you use `dnsmasq` as your DNS server, this may occur if you use the [`--filterwin2k` flag](http://www.thekelleys.org.uk/dnsmasq/docs/setup.html). +In [OpenWRT there is a `filterwin2k` configuration option](https://openwrt.org/docs/guide-user/base-system/dhcp#all_options). +And in [LuCI there is a "Filter useless" option](https://github.com/openwrt/luci/blob/15757dd5b18f9e00ba3c9b38af4d46702a31fe33/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js#L217-L219). +By enabling this flag, `dnsmasq` drops all `SOA` records. + +## March 2020 Let's Encrypt CAA Rechecking Bug +Following the [announcement on March 4](https://community.letsencrypt.org/t/revoking-certain-certificates-on-march-4/114864) Let's Encrypt will be revoking a number of certificates due to a bug in the way they validate CAA records, we have created a tool to analyse your existing cert-manager managed certificates and compare their serial numbers to the publicised list of revoked certificates. +It's advised that all users of Let's Encrypt & cert-manager run a check using this tool to ensure they do not experience any invalid certificate errors in clusters. +You can find a copy of the checker tool here: https://github.com/jetstack/letsencrypt-caa-bug-checker. diff --git a/content/v1.12-docs/troubleshooting/webhook.md b/content/v1.12-docs/troubleshooting/webhook.md new file mode 100644 index 0000000000..b842dcb9de --- /dev/null +++ b/content/v1.12-docs/troubleshooting/webhook.md @@ -0,0 +1,1071 @@ +--- +title: The Definitive Debugging Guide for the cert-manager Webhook Pod +description: 'This guide helps you debug communication issues between the Kubernetes API server and the cert-manager webhook Pod.' +--- + +> Last verified: 8 Sept 2022 + +The cert-manager webhook is a pod that runs as part of your cert-manager +installation. When applying a manifest with `kubectl`, the Kubernetes API server +calls the cert-manager webhook over TLS to validate your manifests. This guide +helps you debug communication issues between the Kubernetes API server and the +cert-manager webhook pod. + +The error messages listed in this page are encountered while installing or +upgrading cert-manager, or shortly after installing or upgrading cert-manager +when trying to create a Certificate, Issuer, or any other cert-manager custom +resource. + +In the below diagram, we show the common pattern when debugging an issue with +the cert-manager webhook: when creating a cert-manager custom resource, the API +server connects over TLS to the cert-manager webhook pod. The red cross +indicates that the API server fails talking to the webhook. + +Diagram that shows a kubectl command that aims to create an issuer resource, and an arrow towards the Kubernetes API server, and an arrow between the API server and the webhook that indicates that the API server tries to connect to the webhook. This last arrow is crossed in red. + +The rest of this document presents error messages you may encounter. + +## Error: `connect: connection refused` + +> This issue was reported in 4 GitHub issues ([#2736](https://github.com/jetstack/cert-manager/issues/2736 "Getting WebHook Connection Refused error when using Azure DevOps Pipelines"), [#3133](https://github.com/jetstack/cert-manager/issues/3133 "Failed calling webhook webhook.cert-manager.io: connect: connection refused"), [#3445](https://github.com/jetstack/cert-manager/issues/3445 "Connection refused for cert-manager-webhook service"), [#4425](https://github.com/cert-manager/cert-manager/issues/4425 "Webhook error")), was reported in 1 GitHub issue in an external project ([`aws-load-balancer-controller#1563`](https://github.com/kubernetes-sigs/aws-load-balancer-controller/issues/1563 "Internal error occurred: failed calling webhook webhook.cert-manager.io, no endpoints available")), on Stack Overflow ([`serverfault#1076563`](https://web.archive.org/web/20210903183221/https://serverfault.com/questions/1076563/creating-issuer-for-kubernetes-cert-manager-is-causing-404-and-500-error "Creating issuer for kubernetes cert-manager is causing 404 and 500 error")), and was mentioned in 13 Slack messages that can be listed with the search `in:#cert-manager in:#cert-manager-dev ":443: connect: connection refused"`. This error message can also be found in other projects that are building webhooks ([`kubewarden-controller#110`](https://github.com/kubewarden/kubewarden-controller/issues/110 "Investigate failure on webhooks not ready when installing cert-manager from helm chart: connection refused")). + +Shortly after installing or upgrading cert-manager, you may hit this error when +creating a Certificate, Issuer, or any other cert-manager custom resource. For +example, creating an Issuer resource with the following command: + +```sh +kubectl apply -f- < 10.96.20.99 (webhook pod) TCP 59466 → 443 [SYN] +10.96.20.99 (webhook pod) -> 192.168.1.43 (apiserver) TCP 443 → 59466 [RST, ACK] +``` + +The `RST` packet is sent by the Linux kernel when nothing is listening to the +requested port. The `RST` packet can also be returned by one of the TCP hops, +e.g., a firewall, as detailed in the Stack Overflow page [What can be the +reasons of connection refused errors?](https://stackoverflow.com/a/2333446/3808537) + +Note that firewalls usually don't return an `RST` packet; they usually drop the +`SYN` packet entirely, and you end up with the error message `i/o timeout` or +`context deadline exceeded`. If that is the case, continue your investigation +with the section [Error: `i/o timeout` (connectivity issue)](#io-timeout) and [Error: `context +deadline exceeded`](#context-deadline-exceeded) respectively. + +Let's eliminate the possible causes from the closest to the source of the TCP +connection (the API server) to its destination (the pod `cert-manager-webhook`). + +Let's imagine that the name `cert-manager-webhook.cert-manager.svc` was resolved +to 10.43.183.232. This is a cluster IP. The control plane node, in which the API +server process runs, uses its iptables to rewrite the IP destination using the +pod IP. That might be the first problem: sometimes, no pod IP is associated with +a given cluster IP because the kubelet doesn't fill in the Endpoint resource +with pod IPs as long as the readiness probe doesn't work. + +Let us first check whether it is a problem with the Endpoint resource: + +```sh +kubectl get endpoints -n cert-manager cert-manager-webhook +``` + +A valid output would look like this: + +```text +NAME ENDPOINTS AGE +cert-manager-webhook 10.244.0.2:10250 27d ✅ +``` + +If you have this valid output and have the `connect: connection refused`, then +the issue is deeper in the networking stack. We won't dig into this case, but +you might want to use `tcpdump` and Wireshark to see whether traffic properly +flows from the API server to the node's host namespace. The traffic from the +host namespace to the pod's namespace already works fine since the kubelet was +already able to reach the readiness endpoint. + +Common issues include firewall dropping traffic from the control plane to +workers; for example, the API server on GKE is only allowed to talk to worker +nodes (which is where the cert-manager webhook is running) over port +`10250`. In EKS, your security groups might deny traffic from your control +plane VPC towards your workers VPC over TCP `10250`. + +If you see ``, it indicates that the cert-manager webhook is properly +running but its readiness endpoint can't be reached: + +```text +NAME ENDPOINTS AGE +cert-manager-webhook 236d ❌ +``` + +To fix ``, you will have to check whether the cert-manager-webhook +deployment is healthy. The endpoints stays at `` while the +cert-manager-webhook isn't marked as `healthy`. + +```sh +kubectl get pod -n cert-manager -l app.kubernetes.io/name=webhook +``` + +You should see that the pod is `Running`, and that the number of containers that +are ready is `0/1`: + +```text +NAME READY STATUS RESTARTS AGE +cert-manager-76578c9687-24kmr 0/1 Running 7 (8h ago) 28d ❌ +``` + +We won't be detailing the case where you get `1/1` and `Running`, since it would +indicate an inconsistent state in Kubernetes. + +Continuing with `0/1`, that means the readiness endpoint isn't answering. When +that happens, no endpoint is created. The next step is to figure out why the +readiness endpoint isn't answering. Let us see which port the kubelet is using +when hitting the readiness endpoint: + +```sh +kubectl -n cert-manager get deploy cert-manager-webhook -oyaml | grep -A5 readiness +``` + +In our example, the port that the kubelet will try to hit is 6080: + +```yaml +readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 6080 # ✨ + scheme: HTTP +``` + +Now, let us port-forward to that port and see if `/healthz` works. In a shell +session, run: + +```sh +kubectl -n cert-manager port-forward deploy/cert-manager-webhook 6080 +``` + +In another shell session, run: + +```sh +curl -sS --dump-header - 127.0.0.1:6080/healthz +``` + +The happy output is: + +```http +HTTP/1.1 200 OK ✅ +Date: Tue, 07 Jun 2022 17:16:56 GMT +Content-Length: 0 +``` + +If the readiness endpoint doesn't work, you will see: + +```text +curl: (7) Failed to connect to 127.0.0.1 port 6080 after 0 ms: Connection refused ❌ +``` + +At this point, verify that the readiness endpoint is configured on that same +port. Let us see the logs to check that our webhook is listening on 6080 for its +readiness endpoint: + +```console +$ kubectl logs -n cert-manager -l app.kubernetes.io/name=webhook | head -10 +I0607 webhook.go:129] "msg"="using dynamic certificate generating using CA stored in Secret resource" +I0607 server.go:133] "msg"="listening for insecure healthz connections" "address"=":6081" ❌ +I0607 server.go:197] "msg"="listening for secure connections" "address"=":10250" +I0607 dynamic_source.go:267] "msg"="Updated serving TLS certificate" +... +``` + +In the above example, the issue was a misconfiguration of the readiness port. In +the webhook deployment, the argument `--healthz-port=6081` was mismatched with +the readiness configuration. + + + +## Error: `i/o timeout` (connectivity issue) + +> This error message was reported 26 times on Slack. To list these messages, do a search with `in:#cert-manager in:#cert-manager-dev "443: i/o timeout"`. The error message was reported in 2 GitHub issues ([#2811](https://github.com/cert-manager/cert-manager/issues/2811 "i/o timeout from apiserver when connecting to webhook on k3s"), [#4073](https://github.com/cert-manager/cert-manager/issues/4073 "Internal error occurred: failed calling webhook")) + +```text +Error from server (InternalError): error when creating "STDIN": Internal error occurred: + failed calling webhook "webhook.cert-manager.io": failed to call webhook: + Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": + dial tcp 10.0.0.69:443: i/o timeout +``` + +When the API server tries to talk to the cert-manager webhook, the `SYN` packet +is never answered, and the connection times out. If we were to run tcpdump +inside the webhook's net namespace, we would see: + +```text +192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP 44772 → 443 [SYN] +192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP [TCP Retransmission] 44772 → 443 [SYN] +192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP [TCP Retransmission] 44772 → 443 [SYN] +192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP [TCP Retransmission] 44772 → 443 [SYN] +``` + +This issue is caused by the `SYN` packet being dropped somewhere. + + + +### Cause 1: GKE Private Cluster + +The default Helm configuration should work with GKE private clusters, but +changing `securePort` might break it. + +For context, unlike public GKE clusters where the control plane can freely talk +to pods over any TCP port, the control plane in private GKE clusters can only +talk to the pods in worker nodes over TCP port `10250` and `443`. These two open +ports refer to the `containerPort` inside the pod, not the port called `port` in +the Service resource. + +For it to work, the `containerPort` inside the Deployment must match either +`10250` or `443`; `containerPort` is configured by the Helm value +`webhook.securePort`. By default, `webhook.securePort` is set to `10250`. + +To see if something is off with the `containerPort`, let us start looking at the +Service resource: + +```sh +kubectl get svc -n cert-manager cert-manager-webhook -oyaml +``` + +Looking at the output, we see that the `targetPort` is set to `"https"`: + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: cert-manager-webhook +spec: + ports: + - name: https + port: 443 # ❌ This port is not the cause. + protocol: TCP + targetPort: "https" # 🌟 This port might be the cause. +``` + +The reason the above `port: 443` can't be the cause is because kube-proxy, which +also runs on the control plane node, translates the webhook's cluster IP to a +pod IP, and also translates the above `port: 443` to the value in +`containerPort`. + +To see how what is behind the target port `"https"`, we look at the +Deployment resource: + +```sh +kubectl get deploy -n cert-manager cert-manager-webhook -oyaml | grep -A3 ports: +``` + +The output shows that the `containerPort` is not set to `10250`, meaning that +a new firewall rule will have to be added in Google Cloud. + +```yaml + ports: + - containerPort: 12345 # 🌟 This port matches neither 10250 nor 443. + name: https + protocol: TCP +``` + +To recap, if the above `containerPort` is something other than `443` or `10250` and +you prefer not changing `containerPort` to `10250`, you will have to add a +new firewall rule. You can read the section [Adding a firewall rule in a +GKE private +cluster](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules) +in the Google documentation. + +For context, the reason we did not default `securePort` to `443` is because +binding to `443` requires one additional Linux capability +(`NET_BIND_SERVICE`); on the other side, `10250` doesn't require any +additional capability. + +### Cause 2: EKS on a custom CNI + +If you are on EKS and you are using a custom CNI such as Weave or Calico, +the Kubernetes API server (which is in its own node) might not be able to +reach the webhook pod. This happens because the control plane cannot be +configured to run on a custom CNI on EKS, meaning that the CNIs cannot +enable connectivity between the API server and the pods running in the +worker nodes. + +Supposing that you are using Helm, the workaround is to add the following +value in your `values.yaml` file: + +```yaml +webhook: + hostNetwork: true + securePort: 10260 +``` + +Or if you are using Helm from the command-line, use the following flag: + +```sh +--set webhook.hostNetwork=true --set webhook.securePort=10260 +``` + +By setting `hostNetwork` to `true`, the webhook pod will be run in the +host's network namespace. By running in the host's network namespace, the +webhook pod becomes accessible over the node's IP, which means you will +work around the fact that kube-apiserver can't reach any pod IPs nor +cluster IPs. + +By setting `securePort` to `10260` instead of relying on the default value +(which is `10250`), you will prevent a conflict between the webhook and the +kubelet. The kubelet, which is an agent that runs on every Kubernetes +worker node and runs directly on the host, uses the port `10250` to +expose its internal API to kube-apiserver. + +To understand how `hostnetwork` and `securePort` interact, we have to look +at how the TCP connection is established. When the kube-apiserver process +tries to connect to the webhook pod, kube-proxy (which also runs on control +plane nodes, even without a CNI) kicks in and translates the webhook's +cluster IP to the webhook's host IP: + +```diagram + https://cert-manager-webhook.cert-manager.svc:443/validate + | + |Step 1: resolve to the cluster IP + v + https://10.43.103.211:443/validate + | + |Step 2: send TCP packet + v + src: 172.28.0.1:43021 + dst: 10.43.103.211:443 + | + |Step 3: kube-proxy rewrite (cluster IP to host IP) + v + src: 172.28.0.1:43021 + dst: 172.28.0.2:10260 + | + | control-plane node + | (host IP: 172.28.0.1) +------------|-------------------------------------------------- + | (host IP: 172.28.0.2) + v worker node + +-------------------+ + | webhook pod | + | listens on | + | 172.28.0.2:10260 | + +-------------------+ +``` + +The reason `10250` is used as the default `securePort` is because it works +around another limitation with GKE Private Clusters, as detailed in the +above section [GKE Private Cluster](#gke-private-cluster). + +### Cause 3: Network Policies, Calico + +Assuming that you are using the Helm chart and that you are using the +default value of `webhook.securePort` (which is `10250`), and that you are +using a network policy controller such as Calico, check that there exists a +policy allowing traffic from the API server to the webhook pod over TCP +port `10250`. + +### Cause 4: EKS and Security Groups + +Assuming that you are using the Helm chart and that you are using the +default value of `webhook.securePort` (which is `10250`), you might want to +check that your AWS Security Groups allow TCP traffic over `10250` from the +control plane's VPC to the workers VPC. + +### Other causes + +If none of the above causes apply, you will need to figure out why the +webhook is unreachable. + +To debug reachability issues (i.e., packets being dropped), we advise to +use `tcpdump` along with Wireshark at every TCP hop. You can follow the +article [Debugging Kubernetes Networking: my `kube-dns` is not +working!](https://maelvls.dev/debugging-kubernetes-networking/) to learn +how to use `tcpdump` with Wireshark to debug networking issues. + +## Error: `x509: certificate is valid for xxx.internal, not cert-manager-webhook.cert-manager.svc` (EKS with Fargate pods) + +```text +Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + x509: certificate is valid for ip-192-168-xxx-xxx.xxx.compute.internal, + not cert-manager-webhook.cert-manager.svc +``` + +> This issue was first reported in +> [#3237](https://github.com/cert-manager/cert-manager/issues/3237 "Can't +> create an issuer when cert-manager runs on EKS in Fargate pods (AWS)"). + +This is probably because you are running on EKS with Fargate enabled. +Fargate creates a microVM per pod, and the VM's kernel is used to run the +container in its own namespace. The problem is that each microVM gets its +own kubelet. As for any Kubernetes node, the VM's port `10250` is listened to +by a kubelet process. And `10250` is also the port that the cert-manager +webhook listens on. + +But that's not a problem: the kubelet process and the cert-manager webhook +process are running in two separate network namespaces, and ports don't +clash. That's the case both in traditional Kubernetes nodes, as well as +inside a Fargate microVM. + +The problem arises when the API server tries hitting the Fargate pod: the +microVM's host net namespace is configured to port-forward every possible port +for maximum compatibility with traditional pods, as demonstrated in the Stack +Overflow page [EKS Fargate connect to local kubelet][66445207]. But the port +`10250` is already used by the microVM's kubelet, so anything hitting this port +won't be port-forwarded and will hit the kubelet instead. + +[66445207]: https://stackoverflow.com/questions/66445207 "EKS Fargate connect to local kubelet" + +To sum up, the cert-manager webhook looks healthy and is able to listen to port +`10250` as per its logs, but the microVM's host does not port-forward `10250` to the +webhook's net namespace. That's the reason you see a message about an unexpected +domain showing up when doing the TLS handshake: although the cert-manager +webhook is properly running, the kubelet is the one responding to the API +server. + +This is a limitation of Fargate's microVMs: the IP of the pod and the IP of the +node are the same. It gives you the same experience as traditional pods, but it +poses networking challenges. + +To fix the issue, the trick is to change the port the cert-manager webhook is +listening on. Using Helm, we can use the parameter `webhook.securePort`: + +```sh +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.8.0 \ + --set webhook.securePort=10260 +``` + +## Error: `service "cert-managercert-manager-webhook" not found` + +```text +Error from server (InternalError): error when creating "test-resources.yaml": Internal error occurred: + failed calling webhook "webhook.cert-manager.io": failed to call webhook: + Post "https://cert-managercert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": + service "cert-managercert-manager-webhook" not found +``` + +> This error was reported in 2 GitHub issues ([#3195](https://github.com/jetstack/cert-manager/issues/3195 "service cert-manager-webhook not found"), +> [#4999](https://github.com/cert-manager/cert-manager/issues/4999 "Verification on 1.7.2 fails (Kubectl apply), service cert-manager-webhook not found")). + +We do not know the cause of this error, please comment on one of the GitHub +issues above if you happen to come across it. + +## Error: `no endpoints available for service "cert-manager-webhook"` (OVHCloud) + +```text +Error: INSTALLATION FAILED: Internal error occurred: + failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + no endpoints available for service "cert-manager-webhook" +``` + +> This issue was first reported once in Slack +> ([1](https://kubernetes.slack.com/archives/C4NV3DWUC/p1634118489064400?thread_ts=1592676867.472700&cid=C4NV3DWUC)). + +This error is rare and was only seen in OVHcloud managed Kubernetes clusters, +where the etcd resource quota is quite low. etcd is the database where your +Kubernetes resources (such as pods and deployments) are stored. OVHCloud limits +the disk space used by your resources in etcd. When the limit is reached, the +whole cluster starts behaving erratically and one symptom is that Endpoint +resources aren't created by the kubelet. + +To verify that it is in fact a problem of quota, you should be able to see the +following messages in your kube-apiserver logs: + +```sh +rpc error: code = Unknown desc = ETCD storage quota exceeded +rpc error: code = Unknown desc = quota computation: etcdserver: not capable +rpc error: code = Unknown desc = The OVHcloud storage quota has been reached +``` + +The workaround is to remove some resources such as CertificateRequest resources +to get under the limit, as explained in OVHCloud's [ETCD Quotas error, +troubleshooting](https://docs.ovh.com/gb/en/kubernetes/etcd-quota-error/) page. + +## Error: `x509: certificate has expired or is not yet valid` + +> This error message was reported once in Slack +> ([1](https://kubernetes.slack.com/archives/C4NV3DWUC/p1618579222346800)). + +When using `kubectl apply`: + +```text +Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://kubernetes.default.svc:443/apis/webhook.cert-manager.io/v1beta1/mutations?timeout=30s: + x509: certificate has expired or is not yet valid +``` + +> This error message was reported once in Slack +([1](https://kubernetes.slack.com/archives/C4NV3DWUC/p1618579222346800)). + +Please answer to the above Slack message since we are still unsure as to what +may cause this issue; to get access to the Kubernetes Slack, visit +[https://slack.k8s.io/](https://slack.k8s.io/). + +## Error: `net/http: request canceled while waiting for connection` + +```text +Error from server (InternalError): error when creating "STDIN": + Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) +``` + +> This error message was reported once in Slack +([1](https://kubernetes.slack.com/archives/C4NV3DWUC/p1632849763397100)). + + + +## Error: `context deadline exceeded` + +> This error message was reported in GitHub issues ([2319](https://github.com/cert-manager/cert-manager/issues/2319 "Documenting context deadline exceeded errors relating to the webhook, on bare metal"), [2706](https://github.com/cert-manager/cert-manager/issues/2706 "") [5189](https://github.com/cert-manager/cert-manager/issues/5189 "Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s: context deadline exceeded"), [5004](https://github.com/cert-manager/cert-manager/issues/5004 "After installing cert-manager using kubectl, cmctl check api fails with https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s: context deadline exceeded")), and once [on Stack Overflow](https://stackoverflow.com/questions/72059332/how-can-i-fix-failed-calling-webhook-webhook-cert-manager-io). + +This error appears with cert-manager 0.12 and above when trying to apply an +Issuer or any other cert-manager custom resource after having installed or +upgraded cert-manager: + +```text +Error from server (InternalError): error when creating "STDIN": + Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + context deadline exceeded +``` + +> ℹ️ In older releases of cert-manager (0.11 and below), the webhook relied on +> the [APIService +> mechanism](https://kubernetes.io/docs/tasks/extend-kubernetes/setup-extension-api-server/), +> and the message looked a bit different but the cause was the same: +> +> ```text +> Error from server (InternalError): error when creating "STDIN": +> Internal error occurred: failed calling webhook "webhook.certmanager.k8s.io": +> Post https://kubernetes.default.svc:443/apis/webhook.certmanager.k8s.io/v1beta1/mutations?timeout=30s: +> context deadline exceeded +> ``` + +> ℹ️ The message `context deadline exceeded` also appears when using `cmctl +> check api`. The cause is identical, you can continue reading this section to +> debug it. +> +> ```text +> Not ready: Internal error occurred: failed calling webhook "webhook.cert-manager.io": failed to call webhook: +> Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": +> context deadline exceeded +> ``` + +The trouble with the message `context deadline exceeded` is that it obfuscates +the part of the HTTP connection that timed out. When this message appears, we +can't tell which part of the HTTP interaction timed out. It might be the DNS +resolution, the TCP handshake, the TLS handshake, sending the HTTP request or +receiving the HTTP response. + +> ℹ️ For context, the query parameter `?timeout=30s` that you can see in the +> above error messages is a timeout that the API server decides when calling the +> webhook. It is often set to 10 or 30 seconds. + +The following diagram shows what are the three errors that may be hidden behind +the all-catching "context deadline exceeded" error message, represented by the +outer box, that is usually thrown after 30 seconds: + + + +```diagram + context deadline exceeded + | + 30 seconds | + timeout v + +-------------------------------------------------------------------------+ + | | + | i/o timeout | + | | net/http: TLS handshake timeout | + | 10 seconds | | | + | timeout v | | + |------------+ 30 seconds | net/http: request canceled | + |TCP | timeout v while awaiting headers | + |handshake +---------------------+ | | + |------------| TLS | | | + | | handshake +------------+ 10 seconds | | + | +---------------------| sending | timeout v | + | | request +------------+ | + | +------------|receiving |------+ | + | |resp. header| recv.| | + | +------------+ resp.| | + | | body +-----+ + | +------|other| + | |logic| + | +-----+ + +-------------------------------------------------------------------------+ + <----------> <----------------------------------------------> + connectivity webhook-side + issue issue +``` + +In the rest of the section, we will be trying to trigger one of the three "more +specific" errors: + +- `i/o timeout` is the TCP handshake timeout and comes from + [`DialTimeout`](https://pkg.go.dev/net#DialTimeout) in the Kubernetes + apiserver. The name resolution may be the cause, but usually, this message + appears after the API server sent the `SYN` packet and waited for 10 seconds + for the `SYN-ACK` packet to be received from the cert-manager webhook. +- `net/http: request canceled while waiting for connection (Client.Timeout + exceeded while awaiting headers)` is the HTTP response timeout and comes from + [here](https://github.com/kubernetes/kubernetes/blob/abba1492f/staging/src/k8s.io/apiserver/pkg/util/webhook/webhook.go#L96-L101) + and is configured to [30 + seconds](https://github.com/kubernetes/kubernetes/blob/abba1492f/staging/src/k8s.io/apiserver/pkg/util/webhook/webhook.go#L36-L38). + The Kubernetes API server already sent the HTTP request is is waiting for the + HTTP response headers (e.g., `HTTP/1.1 200 OK`). +- `net/http: TLS handshake timeout` is when the TCP handshake is done, and the + Kubernetes API server sent the initial TLS handshake packet (`ClientHello`) + and waited for 10 seconds for the cert-manager webhook to answer with the + `ServerHello` packet. + +We can sort these three messages in two categories: either it is a connectivity +issue (`SYN` is dropped), or it is a webhook issue (i.e., the TLS certificate is +wrong, or the webhook is not returning any HTTP response): + +| Timeout message | Category | +|-----------------------------------------------------|--------------------| +| `i/o timeout` | connectivity issue | +| `net/http: TLS handshake timeout` | webhook-side issue | +| `net/http: request canceled while awaiting headers` | webhook-side issue | + +The first step is to rule out a webhook-side issue. In your shell session, run +the following: + +```sh +kubectl -n cert-manager port-forward deploy/cert-manager-webhook 10250 +``` + +In another shell session, check that you can reach the webhook: + +```sh +curl -vsS --resolve cert-manager-webhook.cert-manager.svc:10250:127.0.0.1 \ + --service-name cert-manager-webhook-ca \ + --cacert <(kubectl -n cert-manager get secret cert-manager-webhook-ca -ojsonpath='{.data.ca\.crt}' | base64 -d) \ + https://cert-manager-webhook.cert-manager.svc:10250/validate 2>&1 -d@- <<'EOF' | sed '/^* /d; /bytes data]$/d; s/> //; s/< //' +{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"requestKind":{"group":"cert-manager.io","version":"v1","kind":"Certificate"},"requestResource":{"group":"cert-manager.io","version":"v1","resource":"certificates"},"name":"foo","namespace":"default","operation":"CREATE","object":{"apiVersion":"cert-manager.io/v1","kind":"Certificate","spec":{"dnsNames":["foo"],"issuerRef":{"group":"cert-manager.io","kind":"Issuer","name":"letsencrypt"},"secretName":"foo","usages":["digital signature"]}}}} +EOF +``` + +The happy output looks like this: + +```http +POST /validate HTTP/1.1 +Host: cert-manager-webhook.cert-manager.svc:10250 +User-Agent: curl/7.83.0 +Accept: */* +Content-Length: 1299 +Content-Type: application/x-www-form-urlencoded + +HTTP/1.1 200 OK +Date: Wed, 08 Jun 2022 14:52:21 GMT +Content-Length: 2029 +Content-Type: text/plain; charset=utf-8 + +... +"response": { + "uid": "", + "allowed": true +} +``` + +If the response shows `200 OK`, we can rule out a webhook-side issue. Since the +initial error message was `context deadline exceeded` and not an apiserver-side +issue such as `x509: certificate signed by unknown authority` or `x509: +certificate has expired or is not yet valid`, we can conclude that the problem +is a connectivity issue: the Kubernetes API server isn't able to establish a TCP +connection to the cert-manager webhook. Please follow the instructions in the +section [Error: `i/o timeout` (connectivity issue)](#io-timeout) above to +continue debugging. + +## Error: `net/http: TLS handshake timeout` + +> This error message was reported in 1 GitHub issue ([#2602](https://github.com/cert-manager/cert-manager/issues/2602 "Internal error occurred: failed calling webhook webhook.cert-manager.io: Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: net/http: TLS handshake timeout")). + +```text +Error from server (InternalError): error when creating "STDIN": + Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + net/http: TLS handshake timeout +``` + +Looking at the [above diagram](#diagram), this error message indicates that the +Kubernetes API server successfully established a TCP connection to the pod IP +associated with the cert-manager webhook. The TLS handshake timeout means that +the cert-manager webhook process isn't the one ending the TCP connection: there +is some HTTP proxy in between that is probably waiting for a plain HTTP request +instead a `ClientHello` packet. + +We do not know the cause of this error. Please comment on the above GitHub +issue if you notice this error. + +## Error: `HTTP probe failed with statuscode: 500` + +> This error message was reported in 2 GitHub issue ([#3185](https://github.com/cert-manager/cert-manager/issues/3185 "kubectl install cert-manager: Readiness probe failed: HTTP probe failed with statuscode: 500"), [#4557](https://github.com/cert-manager/cert-manager/issues/4557 "kubectl install cert-manager: Readiness probe failed: HTTP probe failed with statuscode: 500")). + +The error message is visible as an event on the cert-manager webhook: + +```text +Warning Unhealthy (x13 over 15s) kubelet, node83 + Readiness probe failed: HTTP probe failed with statuscode: 500 +``` + +We do not know the cause of this error. Please comment on the above GitHub +issue if you notice this error. + +## Error: `Service Unavailable` + +> This error was reported in 1 GitHub issue ([#4281](https://github.com/cert-manager/cert-manager/issues/4281 "Can't deploy Issuer, Service Unavailable")) + +```text +Error from server (InternalError): error when creating "STDIN": Internal error occurred: + failed calling webhook "webhook.cert-manager.io": + Post "https://my-cert-manager-webhook.default.svc:443/mutate?timeout=10s": + Service Unavailable +``` + +The above message appears in Kubernetes clusters using the Weave CNI. + +We do not know the cause of this error. Please comment on the above GitHub +issue if you notice this error. + +## Error: `failed calling admission webhook: the server is currently unable to handle the request` + +> This issue was reported in 4 GitHub issues ([1369](https://github.com/cert-manager/cert-manager/issues/1369 "the server is currently unable to handle the request"), [1425](https://github.com/cert-manager/cert-manager/issues/1425 "Verifying Install: failed calling admission webhook (Azure, GKE private cluster)") [3542](https://github.com/cert-manager/cert-manager/issues/3542 "SSL Certificate Manager has got expired, we need to renew SSL certificate in existing ClusterIssuer Kubernetes Service (AKS)"), [4852](https://github.com/cert-manager/cert-manager/issues/4852 "error: unable to retrieve the complete list of server APIs: webhook.cert-manager.io/v1beta1: the server is currently unable to handle the request (AKS)")) + +```text +Error from server (InternalError): error when creating "test-resources.yaml": Internal error occurred: + failed calling admission webhook "issuers.admission.certmanager.k8s.io": + the server is currently unable to handle the request +``` + +We do not know the cause of this error. Please comment in one of the above +GitHub issues if you are able to reproduce this error. + +## Error: `x509: certificate signed by unknown authority` + +> Reported in GitHub issues +> ([2602](https://github.com/cert-manager/cert-manager/issues/2602#issuecomment-606474055 "x509: certificate signed by unknown authority")) + +When installing or upgrading cert-manager and using a namespace that is not +`cert-manager`: + +```text +Error: UPGRADE FAILED: release core-l7 failed, and has been rolled back due to atomic being set: + failed to create resource: conversion webhook for cert-manager.io/v1alpha3, Kind=ClusterIssuer failed: + Post https://cert-manager-webhook.core-l7.svc:443/convert?timeout=30s: + x509: certificate signed by unknown authority +``` + +A very similar error message may show when creating an Issuer or any other +cert-manager custom resource: + +```text +Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + x509: certificate signed by unknown authority` +``` + +With `cmctl install` and `cmctl check api`, you might see the following error +message: + +```text +2022/06/06 15:36:30 Not ready: the cert-manager webhook CA bundle is not injected yet + (Internal error occurred: conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: + Post "https://-cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": + x509: certificate signed by unknown authority) +``` + +If you are using cert-manager 0.14 and below with Helm, and that you are +installing in a namespace different from `cert-manager`, the CRD manifest had +the namespace name `cert-manager` hardcoded. You can see the hardcoded namespace +in the following annotation: + +```sh +kubectl get crd issuers.cert-manager.io -oyaml | grep inject +``` + +You will see the following: + +```yaml +cert-manager.io/inject-ca-from-secret: cert-manager/cert-manager-webhook-ca +# ^^^^^^^^^^^^ +# hardcoded +``` + +> **Note 1:** this bug in the cert-manager Helm chart was [was +fixed](https://github.com/cert-manager/cert-manager/commit/f33beefc) in +cert-manager 0.15. +> +> **Note 2:** since cert-manager 1.6, this annotation is [no longer +> used](https://github.com/cert-manager/cert-manager/pull/4841) on the +> cert-manager CRDs since conversion is no longer needed. + +The solution, if you are still using cert-manager 0.14 or below, is to render +the manifest using `helm template`, then edit the annotation to use the correct +namespace, and then use `kubectl apply` to install cert-manager. + +If you are using cert-manager 1.6 and below, the issue might be due to the +cainjector being stuck trying to inject the self-signed certificate that the +cert-manager webhook created and stored in the Secret resource +`cert-manager-webhook-ca` into the `spec.caBundle` field of the cert-manager +CRDs. The first step is to check whether the cainjector is running with no +problem: + +```console +$ kubectl -n cert-manager get pods -l app.kubernetes.io/name=cainjector +NAME READY STATUS RESTARTS AGE +cert-manager-cainjector-5c55bb7cb4-6z4cf 1/1 Running 11 (31h ago) 28d +``` + +Looking at the logs, you will be able to tell if the leader election worked. It +can take up to one minute for the leader election work to complete. + +```console +I0608 start.go:126] "starting" version="v1.8.0" revision="e466a521bc5455def8c224599c6edcd37e86410c" +I0608 leaderelection.go:248] attempting to acquire leader lease kube-system/cert-manager-cainjector-leader-election... +I0608 leaderelection.go:258] successfully acquired lease kube-system/cert-manager-cainjector-leader-election +I0608 controller.go:186] cert-manager/secret/customresourcedefinition/controller/controller-for-secret-customresourcedefinition "msg"="Starting Controller" +I0608 controller.go:186] cert-manager/certificate/customresourcedefinition/controller/controller-for-certificate-customresourcedefinition "msg"="Starting Controller" +I0608 controller.go:220] cert-manager/secret/customresourcedefinition/controller/controller-for-secret-customresourcedefinition "msg"="Starting workers" "worker count"=1 +I0608 controller.go:220] cert-manager/certificate/customresourcedefinition/controller/controller-for-certificate-customresourcedefinition "msg"="Starting workers" "worker count"=1 +``` + +The happy output contains lines like this: + +```console +I0608 sources.go:184] cert-manager/secret/customresourcedefinition/generic-inject-reconciler + "msg"="Extracting CA from Secret resource" "resource_name"="issuers.cert-manager.io" "secret"="cert-manager/cert-manager-webhook-ca" +I0608 controller.go:178] cert-manager/secret/customresourcedefinition/generic-inject-reconciler + "msg"="updated object" "resource_name"="issuers.cert-manager.io" +``` + +Now, look for any message that indicates that the Secret resource that the +cert-manager webhook created can't be loaded. The two error messages that might +show up are: + +```text +E0608 sources.go:201] cert-manager/secret/customresourcedefinition/generic-inject-reconciler + "msg"="unable to fetch associated secret" "error"="Secret \"cert-manager-webhook-caq\" not found" +``` + +The following message indicates that the given CRD has been skipped because the +annotation is missing. You can ignore these messages: + +```text +I0608 controller.go:156] cert-manager/secret/customresourcedefinition/generic-inject-reconciler + "msg"="failed to determine ca data source for injectable" "resource_name"="challenges.acme.cert-manager.io" +``` + +If nothing seems wrong with the cainjector logs, you will want to check that the +`spec.caBundle` field in the validation, mutation, and conversion configurations +are correct. The Kubernetes API server uses the contents of that field to trust +the cert-manager webhook. The `caBundle` contains the self-signed CA created by +the cert-manager webhook when it started. + +```console +$ kubectl get validatingwebhookconfigurations cert-manager-webhook -ojson | jq '.webhooks[].clientConfig' +{ + "caBundle": "LS0tLS1...LS0tLS0K", + "service": { + "name": "cert-manager-webhook", + "namespace": "cert-manager", + "path": "/validate", + "port": 443 + } +} +``` + +```console +$ kubectl get mutatingwebhookconfigurations cert-manager-webhook -ojson | jq '.webhooks[].clientConfig' +{ + "caBundle": "LS0tLS1...RFLS0tLS0K", + "service": { + "name": "cert-manager-webhook", + "namespace": "cert-manager", + "path": "/validate", + "port": 443 + } +} +``` + +Let us see the contents of the `caBundle`: + +```console +$ kubectl get mutatingwebhookconfigurations cert-manager-webhook -ojson \ + | jq '.webhooks[].clientConfig.caBundle' -r | base64 -d \ + | openssl x509 -noout -text -in - + +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + ee:8f:4f:c8:55:7b:16:76:d8:6a:a2:e5:94:bc:7c:6b + Signature Algorithm: ecdsa-with-SHA384 + Issuer: CN = cert-manager-webhook-ca + Validity + Not Before: May 10 16:13:37 2022 GMT + Not After : May 10 16:13:37 2023 GMT + Subject: CN = cert-manager-webhook-ca +``` + +Let us check that the contents of `caBundle` works for connecting to the +webhook: + +```console +$ kubectl -n cert-manager get secret cert-manager-webhook-ca -ojsonpath='{.data.ca\.crt}' \ + | base64 -d | openssl x509 -noout -text -in - + +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + ee:8f:4f:c8:55:7b:16:76:d8:6a:a2:e5:94:bc:7c:6b + Signature Algorithm: ecdsa-with-SHA384 + Issuer: CN = cert-manager-webhook-ca + Validity + Not Before: May 10 16:13:37 2022 GMT + Not After : May 10 16:13:37 2023 GMT + Subject: CN = cert-manager-webhook-ca +``` + +Our final test is to try to connect to the webhook using this trust bundle. Let +us port-forward to the webhook pod: + +```sh +kubectl -n cert-manager port-forward deploy/cert-manager-webhook 10250 +``` + +In another shell session, send a `/validate` HTTP request with the following +command: + +```sh +curl -vsS --resolve cert-manager-webhook.cert-manager.svc:10250:127.0.0.1 \ + --service-name cert-manager-webhook-ca \ + --cacert <(kubectl get validatingwebhookconfigurations cert-manager-webhook -ojson | jq '.webhooks[].clientConfig.caBundle' -r | base64 -d) \ + https://cert-manager-webhook.cert-manager.svc:10250/validate 2>&1 -d@- <<'EOF' | sed '/^* /d; /bytes data]$/d; s/> //; s/< //' +{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"requestKind":{"group":"cert-manager.io","version":"v1","kind":"Certificate"},"requestResource":{"group":"cert-manager.io","version":"v1","resource":"certificates"},"name":"foo","namespace":"default","operation":"CREATE","object":{"apiVersion":"cert-manager.io/v1","kind":"Certificate","spec":{"dnsNames":["foo"],"issuerRef":{"group":"cert-manager.io","kind":"Issuer","name":"letsencrypt"},"secretName":"foo","usages":["digital signature"]}}}} +EOF +``` + +You should see a successful HTTP request and response: + +```http +POST /validate HTTP/1.1 +Host: cert-manager-webhook.cert-manager.svc:10250 +User-Agent: curl/7.83.0 +Accept: */* +Content-Length: 1299 +Content-Type: application/x-www-form-urlencoded + +HTTP/1.1 200 OK +Date: Wed, 08 Jun 2022 16:20:45 GMT +Content-Length: 2029 +Content-Type: text/plain; charset=utf-8 + +... +``` + +## Error: `cluster scoped resource "mutatingwebhookconfigurations/" is managed and access is denied` + +> This message was reported in GitHub issue +> [3717](https://github.com/cert-manager/cert-manager/issues/3717 "Cannot +> install on GKE autopilot cluster due to mutatingwebhookconfigurations access +> denied"). + +While installing cert-manager on GKE Autopilot, you will see the following +message: + +```text +Error: rendered manifests contain a resource that already exists. Unable to continue with install: + could not get information about the resource: + mutatingwebhookconfigurations.admissionregistration.k8s.io "cert-manager-webhook" is forbidden: + User "XXXX" cannot get resource "mutatingwebhookconfigurations" in API group "admissionregistration.k8s.io" at the cluster scope: + GKEAutopilot authz: cluster scoped resource "mutatingwebhookconfigurations/" is managed and access is denied +``` + +This error message will appear when using Kubernetes 1.20 and below with GKE +Autopilot. It is due to a [restriction on mutating admission webhooks in GKE +Autopilot](https://github.com/cert-manager/cert-manager/issues/3717). + +As of October 2021, the "rapid" Autopilot release channel has rolled out version +1.21 for Kubernetes masters. Installation via the Helm chart may end in an error +message but cert-manager is reported to be working by some users. Feedback and +PRs are welcome. + +## Error: `the namespace "kube-system" is managed and the request's verb "create" is denied` + +When installing cert-manager on GKE Autopilot with Helm, you will see the +following error message: + +```text +Not ready: the cert-manager webhook CA bundle is not injected yet +``` + +After this failure, you should still see the three pods happily running: + +```console +$ kubectl get pods -n cert-manager +NAME READY STATUS RESTARTS AGE +cert-manager-76578c9687-24kmr 1/1 Running 0 47m +cert-manager-cainjector-b7d47f746-4799n 1/1 Running 0 47m +cert-manager-webhook-7f788c5b6-mspnt 1/1 Running 0 47m +``` + +But looking at either of the logs, you will see the following error message: + +```text +E0425 leaderelection.go:334] error initially creating leader election record: + leases.coordination.k8s.io is forbidden: User "system:serviceaccount:cert-manager:cert-manager-webhook" + cannot create resource "leases" in API group "coordination.k8s.io" in the namespace "kube-system": + GKEAutopilot authz: the namespace "kube-system" is managed and the request's verb "create" is denied +``` + +That is due to a limitation of GKE Autopilot. It is not possible to create +resources in the `kube-system` namespace, and cert-manager uses the well-known +`kube-system` to manage the leader election. To get around the limitation, you +can tell Helm to use a different namespace for the leader election: + +```sh +helm install cert-manager jetstack/cert-manager --version 1.8.0 \ + --namespace cert-manager --create-namespace \ + --set global.leaderElection.namespace=cert-manager +``` diff --git a/content/v1.12-docs/tutorials/README.md b/content/v1.12-docs/tutorials/README.md new file mode 100644 index 0000000000..cc839b6b10 --- /dev/null +++ b/content/v1.12-docs/tutorials/README.md @@ -0,0 +1,38 @@ +--- +title: Tutorials +description: 'cert-manager tutorials: Overview' +--- + +Step-by-step tutorials are a great way to get started with cert-manager, and we provide a few +for you to learn from. Take a look! + +- [Securing Ingresses with NGINX-Ingress and cert-manager](./acme/nginx-ingress.md): Tutorial for deploying NGINX into your + cluster and securing incoming connections with a certificate from Let's Encrypt. +- [GKE + Ingress + Let's Encrypt](./getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md): + Learn how to deploy cert-manager on Google Kubernetes Engine and how to configure it to get certificates for Ingress, from Let's Encrypt. +- [AKS + LoadBalancer + Let's Encrypt](getting-started-aks-letsencrypt/README.md): + Learn how to deploy cert-manager on Azure Kubernetes Service (AKS) and how to configure it to get certificates for an HTTPS web server, from Let's Encrypt. +- [Backup and Restore Resources](./backup.md): Backup the cert-manager resources + in your cluster and then restore them. +- [Pomerium Ingress](./acme/pomerium-ingress.md): Tutorial on using the Pomerium Ingress Controller with cert-manager. +- [Issuing an ACME Certificate using DNS Validation](./acme/dns-validation.md): + Tutorial on how to resolve DNS ownership validation using DNS01 challenges. +- [Issuing an ACME Certificate using HTTP Validation](./acme/http-validation.md): + Tutorial on how to resolve DNS ownership validation using HTTP01 challenges. +- [Migrating from kube-lego](./acme/migrating-from-kube-lego.md): Tutorial on + how to migrate from the now deprecated kube-lego project. +- [Securing an EKS Cluster with Venafi](./venafi/venafi.md): Tutorial for + creating an EKS cluster and securing an NGINX deployment with a Venafi issued + certificate. +- [Securing an Istio service mesh with cert-manager](./istio-csr/istio-csr.md): Tutorial for + securing an Istio service mesh using a cert-manager issuer. +- [Syncing Secrets Across Namespaces](./syncing-secrets-across-namespaces.md): + Learn how to synchronize Kubernetes Secret resources across namespaces using extensions such as: reflector, kubed and kubernetes-replicator. +- [Obtaining SSL certificates with the ZeroSSL](./zerossl/zerossl.md): Tutorial describing usage of the ZeroSSL as external ACME server. + +### External Tutorials + +- A great AWS blog post on using cert-manager for end-to-end encryption in EKS. See [Setting up end-to-end TLS encryption on Amazon EKS](https://aws.amazon.com/blogs/containers/setting-up-end-to-end-tls-encryption-on-amazon-eks-with-the-new-aws-load-balancer-controller/) +- A full cert-manager installation demo on a GKE Cluster. See [How-To: Automatic SSL Certificate Management for your Kubernetes Application Deployment](https://medium.com/contino-engineering/how-to-automatic-ssl-certificate-management-for-your-kubernetes-application-deployment-94b64dfc9114) +- cert-manager installation on GKE Cluster using Workload Identity. See [Kubernetes, ingress-nginx, cert-manager & external-dns](https://blog.atomist.com/kubernetes-ingress-nginx-cert-manager-external-dns/) +- A video tutorial for beginners showing cert-manager in action. See [Free SSL for Kubernetes with cert-manager](https://www.youtube.com/watch?v=hoLUigg4V18) diff --git a/content/v1.12-docs/tutorials/acme/dns-validation.md b/content/v1.12-docs/tutorials/acme/dns-validation.md new file mode 100644 index 0000000000..bddf11097b --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/dns-validation.md @@ -0,0 +1,169 @@ +--- +title: DNS Validation +description: 'cert-manager turorials: Issuing an ACME certificate using DNS validation' +--- + +## Issuing an ACME certificate using DNS validation + +cert-manager can be used to obtain certificates from a CA using the +[ACME](https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment) +protocol. The ACME protocol supports various challenge mechanisms which are +used to prove ownership of a domain so that a valid certificate can be issued +for that domain. + +One such challenge mechanism is DNS01. With a DNS01 challenge, you prove +ownership of a domain by proving you control its DNS records. +This is done by creating a TXT record with specific content that proves you +have control of the domains DNS records. + +The following Issuer defines the necessary information to enable DNS validation. +You can read more about the Issuer resource in the [Issuer +docs](../../configuration/README.md). + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging + namespace: default +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: user@example.com + + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + + # ACME DNS-01 provider configurations + solvers: + # An empty 'selector' means that this solver matches all domains + - selector: {} + dns01: + cloudDNS: + # The ID of the GCP project + # reference: https://cert-manager.io/docs/tutorials/acme/dns-validation/ + project: $PROJECT_ID + # This is the secret used to access the service account + serviceAccountSecretRef: + name: clouddns-dns01-solver-svc-acct + key: key.json + + # We only use cloudflare to solve challenges for example.org. + # Alternative options such as 'matchLabels' and 'dnsZones' can be specified + # as part of a solver's selector too. + - selector: + dnsNames: + - example.org + dns01: + cloudflare: + email: my-cloudflare-acc@example.com + # !! Remember to create a k8s secret before + # kubectl create secret generic cloudflare-api-key-secret + apiKeySecretRef: + name: cloudflare-api-key-secret + key: api-key +``` + + +We have specified the ACME server URL for Let's Encrypt's [staging +environment](https://letsencrypt.org/docs/staging-environment/). The staging +environment will not issue trusted certificates but is used to ensure that the +verification process is working properly before moving to production. Let's +Encrypt's production environment imposes much stricter [rate +limits](https://letsencrypt.org/docs/rate-limits/), so to reduce the chance of +you hitting those limits it is highly recommended to start by using the staging +environment. To move to production, simply create a new Issuer with the URL set +to `https://acme-v02.api.letsencrypt.org/directory`. + +The first stage of the ACME protocol is for the client to register with the +ACME server. This phase includes generating an asymmetric key pair which is +then associated with the email address specified in the Issuer. Make sure to +change this email address to a valid one that you own. It is commonly used to +send expiry notices when your certificates are coming up for renewal. The +generated private key is stored in a Secret named `letsencrypt-staging`. + +The `dns01` stanza contains a list of DNS01 providers that can be used to +solve DNS challenges. Our Issuer defines two providers. This gives us a choice +of which one to use when obtaining certificates. + +More information about the DNS provider configuration, including a list of +supported providers, can be found [in the DNS01 reference docs](../../configuration/acme/dns01/README.md). + +Once we have created the above Issuer we can use it to obtain a certificate. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: default +spec: + secretName: example-com-tls + issuerRef: + name: letsencrypt-staging + dnsNames: + - '*.example.com' + - example.com + - example.org +``` + +The Certificate resource describes our desired certificate and the possible +methods that can be used to obtain it. You can obtain certificates for wildcard +domains just like any other. Make sure to wrap wildcard domains with asterisks +in your YAML resources, to avoid formatting issues. If you specify both +`example.com` and `*.example.com` on the same Certificate, it will take slightly +longer to perform validation as each domain will have to be validated one after +the other. You can learn more about the Certificate resource in the +[docs](../../usage/README.md). If the certificate is obtained successfully, the +resulting key pair will be stored in a secret called `example-com-tls` in the +same namespace as the Certificate. + +The certificate will have a common name of `*.example.com` and the [Subject +Alternative Names +(SANs)](https://en.wikipedia.org/wiki/Subject_Alternative_Name) will be +`*.example.com`, `example.com` and `example.org`. + +In our Certificate we have referenced the `letsencrypt-staging` Issuer above. +The Issuer must be in the same namespace as the Certificate. If you want to +reference a `ClusterIssuer`, which is a cluster-scoped version of an Issuer, you +must add `kind: ClusterIssuer` to the `issuerRef` stanza. + +For more information on `ClusterIssuers`, read the +[issuer concepts](../../concepts/issuer.md). + +The `acme` stanza defines the configuration for our ACME challenges. Here we +have defined the configuration for our DNS challenges which will be used to +verify domain ownership. For each domain mentioned in a `dns01` stanza, +cert-manager will use the provider's credentials from the referenced Issuer to +create a TXT record called `_acme-challenge`. This record will then be verified +by the ACME server in order to issue the certificate. Once domain ownership has +been verified, any cert-manager affected records will be cleaned up. + +> Note: It is your responsibility to ensure the selected provider is +> authoritative for your domain. + +After creating the above Certificate, we can check whether it has been obtained +successfully using `kubectl describe`: + +```bash +$ kubectl describe certificate example-com +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CreateOrder 57m cert-manager Created new ACME order, attempting validation... + Normal DomainVerified 55m cert-manager Domain "*.example.com" verified with "dns-01" validation + Normal DomainVerified 55m cert-manager Domain "example.com" verified with "dns-01" validation + Normal DomainVerified 55m cert-manager Domain "example.org" verified with "dns-01" validation + Normal IssueCert 55m cert-manager Issuing certificate... + Normal CertObtained 55m cert-manager Obtained certificate from ACME server + Normal CertIssued 55m cert-manager Certificate issued successfully +``` + +You can also check whether issuance was successful with `kubectl get secret +example-com-tls -o yaml`. You should see a base64 encoded signed TLS key pair. + +Once our certificate has been obtained, cert-manager will periodically check its +validity and attempt to renew it if it gets close to expiry. cert-manager +considers certificates to be close to expiry when the 'Not After' field on the +certificate is less than the current time plus 30 days. \ No newline at end of file diff --git a/content/v1.12-docs/tutorials/acme/example/deployment.yaml b/content/v1.12-docs/tutorials/acme/example/deployment.yaml new file mode 100644 index 0000000000..d876c66cf9 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/deployment.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kuard +spec: + selector: + matchLabels: + app: kuard + replicas: 1 + template: + metadata: + labels: + app: kuard + spec: + containers: + - image: gcr.io/kuar-demo/kuard-amd64:1 + imagePullPolicy: Always + name: kuard + ports: + - containerPort: 8080 diff --git a/content/v1.12-docs/tutorials/acme/example/ingress-tls-final.yaml b/content/v1.12-docs/tutorials/acme/example/ingress-tls-final.yaml new file mode 100644 index 0000000000..5c90402c41 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/ingress-tls-final.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kuard + annotations: + kubernetes.io/ingress.class: "nginx" + cert-manager.io/issuer: "letsencrypt-prod" + +spec: + tls: + - hosts: + - example.example.com + secretName: quickstart-example-tls + rules: + - host: example.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kuard + port: + number: 80 diff --git a/content/v1.12-docs/tutorials/acme/example/ingress-tls.yaml b/content/v1.12-docs/tutorials/acme/example/ingress-tls.yaml new file mode 100644 index 0000000000..f888087d67 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/ingress-tls.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kuard + annotations: + kubernetes.io/ingress.class: "nginx" + cert-manager.io/issuer: "letsencrypt-staging" + +spec: + tls: + - hosts: + - example.example.com + secretName: quickstart-example-tls + rules: + - host: example.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kuard + port: + number: 80 diff --git a/content/v1.12-docs/tutorials/acme/example/ingress.yaml b/content/v1.12-docs/tutorials/acme/example/ingress.yaml new file mode 100644 index 0000000000..a2b8f8c4bd --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kuard + annotations: + kubernetes.io/ingress.class: "nginx" + #cert-manager.io/issuer: "letsencrypt-staging" + +spec: + tls: + - hosts: + - example.example.com + secretName: quickstart-example-tls + rules: + - host: example.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kuard + port: + number: 80 diff --git a/content/v1.12-docs/tutorials/acme/example/pomerium-certificates.yaml b/content/v1.12-docs/tutorials/acme/example/pomerium-certificates.yaml new file mode 100644 index 0000000000..7e07111417 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/pomerium-certificates.yaml @@ -0,0 +1,36 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: pomerium-cert + namespace: pomerium +spec: + secretName: pomerium-tls + issuerRef: + name: pomerium-issuer + kind: Issuer + usages: + - server auth + - client auth + dnsNames: + - pomerium-proxy.pomerium.svc.cluster.local + - pomerium-authorize.pomerium.svc.cluster.local + - pomerium-databroker.pomerium.svc.cluster.local + - pomerium-authenticate.pomerium.svc.cluster.local +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: pomerium-redis-cert + namespace: pomerium +spec: + secretName: pomerium-redis-tls + issuerRef: + name: pomerium-issuer + kind: Issuer + usages: + - server auth + - client auth + dnsNames: + - pomerium-redis-master.pomerium.svc.cluster.local + - pomerium-redis-headless.pomerium.svc.cluster.local + - pomerium-redis-replicas.pomerium.svc.cluster.local diff --git a/content/v1.12-docs/tutorials/acme/example/pomerium-production-issuer.yaml b/content/v1.12-docs/tutorials/acme/example/pomerium-production-issuer.yaml new file mode 100644 index 0000000000..d802d4d07f --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/pomerium-production-issuer.yaml @@ -0,0 +1,19 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-prod + namespace: pomerium +spec: + acme: + # The ACME server URL + server: https://acme-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-prod + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + class: pomerium \ No newline at end of file diff --git a/content/v1.12-docs/tutorials/acme/example/pomerium-staging-issuer.yaml b/content/v1.12-docs/tutorials/acme/example/pomerium-staging-issuer.yaml new file mode 100644 index 0000000000..f7756ed4bb --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/pomerium-staging-issuer.yaml @@ -0,0 +1,19 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging + namespace: pomerium +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + class: pomerium \ No newline at end of file diff --git a/content/v1.12-docs/tutorials/acme/example/pomerium-values.yaml b/content/v1.12-docs/tutorials/acme/example/pomerium-values.yaml new file mode 100644 index 0000000000..2460377547 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/pomerium-values.yaml @@ -0,0 +1,39 @@ +authenticate: + existingTLSSecret: pomerium-tls + idp: + provider: "google" + clientID: YOUR_CLIENT_ID + clientSecret: YOUR_SECRET + serviceAccount: YOUR_SERVICE_ACCOUNT + ingress: + annotations: + cert-manager.io/issuer: letsencrypt-staging + tls: + secretName: authenticate.localhost.pomerium.io-tls + +proxy: + existingTLSSecret: pomerium-tls + +databroker: + existingTLSSecret: pomerium-tls + storage: + clientTLS: + existingSecretName: pomerium-redis-tls + existingCASecretKey: ca.crt + +authorize: + existingTLSSecret: pomerium-tls + +redis: + enabled: true + generateTLS: false + tls: + certificateSecret: pomerium-redis-tls + +ingressController: + enabled: true + +config: + rootDomain: localhost.pomerium.io #Change this to your reserved domain space. + existingCASecret: pomerium-tls + generateTLS: false # On by default, disabled when cert-manager or another solution is in place. diff --git a/content/v1.12-docs/tutorials/acme/example/production-issuer.yaml b/content/v1.12-docs/tutorials/acme/example/production-issuer.yaml new file mode 100644 index 0000000000..f7676f2522 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/production-issuer.yaml @@ -0,0 +1,18 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-prod +spec: + acme: + # The ACME server URL + server: https://acme-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-prod + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + class: nginx diff --git a/content/v1.12-docs/tutorials/acme/example/service.yaml b/content/v1.12-docs/tutorials/acme/example/service.yaml new file mode 100644 index 0000000000..864b481ccb --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: kuard +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + selector: + app: kuard diff --git a/content/v1.12-docs/tutorials/acme/example/staging-issuer.yaml b/content/v1.12-docs/tutorials/acme/example/staging-issuer.yaml new file mode 100644 index 0000000000..b733952029 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/example/staging-issuer.yaml @@ -0,0 +1,18 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + class: nginx diff --git a/content/v1.12-docs/tutorials/acme/http-validation.md b/content/v1.12-docs/tutorials/acme/http-validation.md new file mode 100644 index 0000000000..d5cda8e658 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/http-validation.md @@ -0,0 +1,159 @@ +--- +title: HTTP Validation +description: 'cert-manager tutorials: Issuing an ACME certificate using HTTP validation' +--- + +## Issuing an ACME certificate using HTTP validation + +cert-manager can be used to obtain certificates from a CA using the +[ACME](https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment) +protocol. The ACME protocol supports various challenge mechanisms which are +used to prove ownership of a domain so that a valid certificate can be issued +for that domain. + +One such challenge mechanism is the HTTP01 challenge. With a HTTP01 challenge, +you prove ownership of a domain by ensuring that a particular file is present at +the domain. It is assumed that you control the domain if you are able to +publish the given file under a given path. + +The following Issuer defines the necessary information to enable HTTP +validation. You can read more about the Issuer resource in the [Issuer +docs](../../concepts/issuer.md). + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging + namespace: default +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + # Enable the HTTP-01 challenge provider + solvers: + # An empty 'selector' means that this solver matches all domains + - selector: {} + http01: + ingress: + class: nginx +``` + +We have specified the ACME server URL for Let's Encrypt's [staging +environment](https://letsencrypt.org/docs/staging-environment/). The staging +environment will not issue trusted certificates but is used to ensure that the +verification process is working properly before moving to production. Let's +Encrypt's production environment imposes much stricter [rate +limits](https://letsencrypt.org/docs/rate-limits/), so to reduce the chance of +you hitting those limits it is highly recommended to start by using the staging +environment. To move to production, simply create a new Issuer with the URL set +to `https://acme-v02.api.letsencrypt.org/directory`. + +The first stage of the ACME protocol is for the client to register with the +ACME server. This phase includes generating an asymmetric key pair which is +then associated with the email address specified in the Issuer. Make sure to +change this email address to a valid one that you own. It is commonly used to +send expiry notices when your certificates are coming up for renewal. The +generated private key is stored in a Secret named `letsencrypt-staging`. + +We must provide one or more Solvers for handling the ACME challenge. In this +case we want to use HTTP validation so we specify an `http01` Solver. We could +optionally map different domains to use different Solver configurations. + +Once we have created the above Issuer we can use it to obtain a certificate. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: default +spec: + secretName: example-com-tls + issuerRef: + name: letsencrypt-staging + commonName: example.com + dnsNames: + - www.example.com +``` + +The Certificate resource describes our desired certificate and the possible +methods that can be used to obtain it. You can learn more about the Certificate +resource in the [docs](../../concepts/certificate.md). If the certificate is +obtained successfully, the resulting key pair will be stored in a secret called +`example-com-tls` in the same namespace as the Certificate. + +The certificate will have a common name of `example.com` and the [Subject +Alternative Names +(SANs)](https://en.wikipedia.org/wiki/Subject_Alternative_Name) will be +`example.com` and `www.example.com`. Note that only these SANs will be respected +by TLS clients. + +In our Certificate we have referenced the `letsencrypt-staging` Issuer above. +The Issuer must be in the same namespace as the Certificate. If you want to +reference a `ClusterIssuer`, which is a cluster-scoped version of an Issuer, you +must add `kind: ClusterIssuer` to the `issuerRef` stanza. + +For more information on `ClusterIssuers`, read the [`ClusterIssuer` +docs](../../concepts/issuer.md). + +The `acme` stanza defines the configuration for our ACME challenges. Here we +have defined the configuration for our HTTP01 challenges which will be used to +verify domain ownership. To verify ownership of each domain mentioned in an +`http01` stanza, cert-manager will create a Pod, Service and Ingress that +exposes an HTTP endpoint that satisfies the HTTP01 challenge. + +The fields `ingress` and `ingressClass` in the `http01` stanza can be used to +control how cert-manager interacts with Ingress resources: + +- If the `ingress` field is specified, then an Ingress resource with the same + name in the same namespace as the Certificate must already exist and it will + be modified only to add the appropriate rules to solve the challenge. + This field is useful for the Google Cloud Loadbalancer ingress controller, + as well as a number of others, that assign a single public IP address for + each ingress resource. + Without manual intervention, creating a new ingress resource would cause any + challenges to fail. +- If the `ingressClass` field is specified, a new ingress resource with a + randomly generated name will be created in order to solve the challenge. + This new resource will have an annotation with key `kubernetes.io/ingress.class` + and value set to the value of the `ingressClass` field. + This works for the likes of the NGINX ingress controller. +- If neither are specified, new ingress resources will be created with a randomly + generated name, but they will not have the ingress class annotation set. +- If both are specified, then the `ingress` field will take precedence. + +Once domain ownership has been verified, any cert-manager affected resources will +be cleaned up or deleted. + +> Note: It is your responsibility to point each domain name at the correct IP +> address for your ingress controller. + +After creating the above Certificate, we can check whether it has been obtained +successfully using `kubectl describe`: + +```bash +$ kubectl describe certificate example-com +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CreateOrder 57m cert-manager Created new ACME order, attempting validation... + Normal DomainVerified 55m cert-manager Domain "example.com" verified with "http-01" validation + Normal DomainVerified 55m cert-manager Domain "www.example.com" verified with "http-01" validation + Normal IssueCert 55m cert-manager Issuing certificate... + Normal CertObtained 55m cert-manager Obtained certificate from ACME server + Normal CertIssued 55m cert-manager Certificate issued successfully +``` + +You can also check whether issuance was successful with `kubectl get secret +example-com-tls -o yaml`. You should see a base64 encoded signed TLS key pair. + +Once our certificate has been obtained, cert-manager will periodically check its +validity and attempt to renew it if it gets close to expiry. cert-manager +considers certificates to be close to expiry when the 'Not After' field on the +certificate is less than the current time plus 30 days. \ No newline at end of file diff --git a/content/v1.12-docs/tutorials/acme/migrating-from-kube-lego.md b/content/v1.12-docs/tutorials/acme/migrating-from-kube-lego.md new file mode 100644 index 0000000000..89b3ffebc9 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/migrating-from-kube-lego.md @@ -0,0 +1,232 @@ +--- +title: Migrating from Kube-LEGO +description: 'cert-manager tutorials: Migrating from kube-lego' +--- + +[kube-lego](https://github.com/jetstack/kube-lego) is an older Jetstack project +for obtaining TLS certificates from Let's Encrypt (or another ACME server). + +Since cert-managers release, kube-lego has been gradually deprecated in favor +of this project. There are a number of key differences between the two: + +| Feature | kube-lego | cert-manager | +|-------------------------------------------|----------------------------------|------------------------| +| Configuration | Annotations on Ingress resources | CRDs | +| CAs | ACME | ACME, signing key pair | +| Kubernetes | `v1.2` - `v1.8` | `v1.7+` | +| Debugging | Look at logs | Kubernetes Events API | +| Multi-tenancy | Not supported | Supported | +| Distinct issuance sources per Certificate | Not supported | Supported | +| Ingress controller support (ACME) | GCE, NGINX | All | + +This guide will walk through how you can safely migrate your kube-lego +installation to cert-manager, without service interruption. + +By the end of the guide, we should have: + +1. Scaled down and removed kube-lego + +2. Installed cert-manager + +3. Migrated ACME private key to cert-manager + +4. Created an ACME `ClusterIssuer` using this private key, to issue certificates + throughout your cluster + +5. Configured cert-manager's [`ingress-shim`](../../usage/ingress.md) to + automatically provision Certificate resources for all Ingress resources with + the `kubernetes.io/tls-acme: "true"` annotation, using the `ClusterIssuer` we + have created + +6. Verified that the cert-manager installation is working + + +## 1. Scale down kube-lego + +Before we begin deploying cert-manager, it is best we scale our kube-lego +deployment down to 0 replicas. This will prevent the two controllers +potentially 'fighting' each other. If you deployed kube-lego using the official +deployment YAMLs, a command like so should do: + +```bash +$ kubectl scale deployment kube-lego \ + --namespace kube-lego \ + --replicas=0 +``` + +You can then verify your kube-lego pod is no longer running with: + +```bash +$ kubectl get pods --namespace kube-lego +``` + +## 2. Deploy cert-manager + +cert-manager should be deployed using Helm, according to our official +[installation guide](../../installation/README.md). No special steps are +required here. We will return to this deployment at the end of this guide and +perform an upgrade of some of the CLI flags we deploy cert-manager with however. + +Please take extra care to ensure you have configured RBAC correctly when +deploying Helm and cert-manager - there are some nuances described in our +deploying document! + +## 3. Obtaining your ACME account private key + +In order to continue issuing and renewing certificates on your behalf, we need +to migrate the user account private key that kube-lego has created for you over +to cert-manager. + +Your ACME user account identity is a private key, stored in a secret resource. +By default, kube-lego will store this key in a secret named `kube-lego-account` +in the same namespace as your kube-lego Deployment. You may have overridden this +value when you deploy kube-lego, in which case the secret name to use will be +the value of the `LEGO_SECRET_NAME` environment variable. + +You should download a copy of this secret resource and save it in your local +directory: + +```bash +$ kubectl get secret kube-lego-account -o yaml \ + --namespace kube-lego \ + --export > kube-lego-account.yaml +``` + +Once saved, open up this file and change the `metadata.name` field to something +more relevant to cert-manager. For the rest of this guide, we'll assume you +chose `letsencrypt-private-key`. + +Once done, we need to create this new resource in the `cert-manager` namespace. +By default, cert-manager stores supporting resources for `ClusterIssuers` in the +namespace that it is running in, and we used `cert-manager` when deploying +cert-manager above. You should change this if you have deployed cert-manager +into a different namespace. + +```bash +$ kubectl create -f kube-lego-account.yaml \ + --namespace cert-manager +``` + +## 4. Creating an ACME `ClusterIssuer` using your old ACME account + +We need to create a `ClusterIssuer` which will hold information about the ACME +account previously registered via kube-lego. In order to do so, we need two more +pieces of information from our old kube-lego deployment: the server URL of the +ACME server, and the email address used to register the account. + +Both of these bits of information are stored within the kube-lego `ConfigMap`. + +To retrieve them, you should be able to `get` the `ConfigMap` using `kubectl`: + +```bash +$ kubectl get configmap kube-lego -o yaml \ + --namespace kube-lego \ + --export +``` + +Your email address should be shown under the `.data.lego.email` field, and the +ACME server URL under `.data.lego.url`. + +For the purposes of this guide, we will assume the email is +`user@example.com` and the URL +`https://acme-staging-v02.api.letsencrypt.org/directory`. + +Now that we have migrated our private key to the new Secret resource, as well as +obtaining our ACME email address and URL, we can create a `ClusterIssuer` +resource! + +Create a file named `cluster-issuer.yaml`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + # Adjust the name here accordingly + name: letsencrypt-staging +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key from step 3 + privateKeySecretRef: + name: letsencrypt-private-key + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + class: nginx +``` + +We then submit this file to our Kubernetes cluster: + +```bash +$ kubectl create -f cluster-issuer.yaml +``` + +You should be able to verify the ACME account has been verified successfully: + +```bash +$ kubectl describe clusterissuer letsencrypt-staging +... +Status: + Acme: + Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/7571319 + Conditions: + Last Transition Time: 2019-01-30T14:52:03Z + Message: The ACME account was registered with the ACME server + Reason: ACMEAccountRegistered + Status: True + Type: Ready +``` + +## 5. Configuring ingress-shim to use our new `ClusterIssuer` by default + +Now that our `ClusterIssuer` is ready to issue certificates, we have one last +thing to do: we must reconfigure `ingress-shim` (deployed as part of cert-manager) +to automatically create Certificate resources for all Ingress resources it finds +with appropriate annotations. + +More information on the role of ingress-shim can be found [in the +docs](../../usage/ingress.md), but for now we can just run a `helm +upgrade` in order to add a few additional flags. Assuming you've named your +`ClusterIssuer` `letsencrypt-staging` (as above), run: + +```bash +$ helm upgrade cert-manager \ + jetstack/cert-manager \ + --namespace cert-manager \ + --set ingressShim.defaultIssuerName=letsencrypt-staging \ + --set ingressShim.defaultIssuerKind=ClusterIssuer +``` + +You should see the cert-manager pod be re-created, and once started it should +automatically create Certificate resources for all of your ingresses that +previously had kube-lego enabled. + +## 6. Verify each ingress now has a corresponding Certificate + +Before we finish, we should make sure there is now a Certificate resource for +each ingress resource you previously enabled kube-lego on. + +You should be able to check this by running: + +```bash +$ kubectl get certificates --all-namespaces +``` + +There should be an entry for each ingress in your cluster with the kube-lego +annotation. + +We can also verify that cert-manager has 'adopted' the old TLS certificates by +viewing the logs for cert-manager: + +```bash +$ kubectl logs -n cert-manager -l app=cert-manager -c cert-manager +... +I1025 21:54:02.869269 1 sync.go:206] Certificate my-example-certificate scheduled for renewal in 292 hours +``` + +Here we can see cert-manager has verified the existing TLS certificate and +scheduled it to be renewed in 292 hours time. \ No newline at end of file diff --git a/content/v1.12-docs/tutorials/acme/nginx-ingress.md b/content/v1.12-docs/tutorials/acme/nginx-ingress.md new file mode 100644 index 0000000000..9a334f4810 --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/nginx-ingress.md @@ -0,0 +1,602 @@ +--- +title: Securing NGINX-ingress +description: 'cert-manager tutorials: Using ingress-nginx to solve an ACME HTTP-01 challenge' +--- + +This tutorial will detail how to install and secure ingress to your cluster +using NGINX. + +## Step 1 - Install Helm + +> *Skip this section if you have helm installed.* + +The easiest way to install `cert-manager` is to use [`Helm`](https://helm.sh), a +templating and deployment tool for Kubernetes resources. + +First, ensure the Helm client is installed following the [Helm installation +instructions](https://helm.sh/docs/intro/install/). + +For example, on MacOS: + +```bash +brew install kubernetes-helm +``` + +## Step 2 - Deploy the NGINX Ingress Controller + +A [`kubernetes ingress controller`](https://kubernetes.io/docs/concepts/services-networking/ingress) is +designed to be the access point for HTTP and HTTPS traffic to the software +running within your cluster. The `ingress-nginx-controller` does this by providing +an HTTP proxy service supported by your cloud provider's load balancer. + +You can get more details about `ingress-nginx` and how it works from the +[documentation for `ingress-nginx`](https://kubernetes.github.io/ingress-nginx/). + +Add the latest helm repository for the ingress-nginx + +```bash +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +``` + +Update the helm repository with the latest charts: + +```bash +$ helm repo update +Hang tight while we grab the latest from your chart repositories... +...Skip local chart repository +...Successfully got an update from the "stable" chart repository +...Successfully got an update from the "ingress-nginx" chart repository +...Successfully got an update from the "coreos" chart repository +Update Complete. ⎈ Happy Helming!⎈ +``` + +Use `helm` to install an NGINX Ingress controller: + +```bash +$ helm install quickstart ingress-nginx/ingress-nginx + +NAME: quickstart +... lots of output ... +``` + +It can take a minute or two for the cloud provider to provide and link a public +IP address. When it is complete, you can see the external IP address using the +`kubectl` command: + +```bash +$ kubectl get svc +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kubernetes ClusterIP 10.0.0.1 443/TCP 13m +quickstart-ingress-nginx-controller LoadBalancer 10.0.114.241 80:31635/TCP,443:30062/TCP 8m16s +quickstart-ingress-nginx-controller-admission ClusterIP 10.0.188.24 443/TCP 8m16s +``` + +This command shows you all the services in your cluster (in the `default` +namespace), and any external IP addresses they have. When you first create the +controller, your cloud provider won't have assigned and allocated an IP address +through the `LoadBalancer` yet. Until it does, the external IP address for the +service will be listed as ``. + +Your cloud provider may have options for reserving an IP address prior to +creating the ingress controller and using that IP address rather than assigning +an IP address from a pool. Read through the documentation from your cloud +provider on how to arrange that. + +## Step 3 - Assign a DNS name + +The external IP that is allocated to the ingress-controller is the IP to which +all incoming traffic should be routed. To enable this, add it to a DNS zone you +control, for example as `www.example.com`. + +This quick-start assumes you know how to assign a DNS entry to an IP address and +will do so. + +## Step 4 - Deploy an Example Service + +Your service may have its own chart, or you may be deploying it directly with +manifests. This quick-start uses manifests to create and expose a sample service. +The example service uses [`kuard`](https://github.com/kubernetes-up-and-running/kuard), +a demo application. + +The quick-start example uses three manifests for the sample. The first two are a +sample deployment and an associated service: + +```yaml file=./example/deployment.yaml +``` + +```yaml file=./example/service.yaml +``` + +You can create download and reference these files locally, or you can +reference them from the GitHub source repository for this documentation. +To install the example service from the tutorial files straight from GitHub, do +the following: + +```bash +kubectl apply -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/deployment.yaml +# expected output: deployment.extensions "kuard" created + +kubectl apply -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/service.yaml +# expected output: service "kuard" created +``` + +An [Ingress resource](https://kubernetes.io/docs/concepts/services-networking/ingress/) is +what Kubernetes uses to expose this example service outside the cluster. You +will need to download and modify the example manifest to reflect the domain that +you own or control to complete this example. + +A sample ingress you can start with is: + +```yaml file=./example/ingress.yaml +``` + +You can download the sample manifest from GitHub , edit it, and submit the +manifest to Kubernetes with the command below. Edit the file in your editor, and once +it is saved: + +```bash +kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/ingress.yaml +# expected output: ingress.networking.k8s.io/kuard created +``` + +> Note: The ingress example we show above has a `host` definition within it. The +> `ingress-nginx-controller` will route traffic when the hostname requested +> matches the definition in the ingress. You *can* deploy an ingress without a +> `host` definition in the rule, but that pattern isn't usable with a TLS +> certificate, which expects a fully qualified domain name. + +Once it is deployed, you can use the command `kubectl get ingress` to see the status + of the ingress: + +```text +NAME HOSTS ADDRESS PORTS AGE +kuard * 80, 443 17s +``` + +It may take a few minutes, depending on your service provider, for the ingress +to be fully created. When it has been created and linked into place, the +ingress will show an address as well: + +```text +NAME HOSTS ADDRESS PORTS AGE +kuard * 203.0.113.2 80 9m +``` + +> Note: The IP address on the ingress *may not* match the IP address that the +> `ingress-nginx-controller` has. This is fine, and is a quirk/implementation detail +> of the service provider hosting your Kubernetes cluster. Since we are using +> the `ingress-nginx-controller` instead of any cloud-provider specific ingress +> backend, use the IP address that was defined and allocated for the +> `quickstart-ingress-nginx-controller ` `LoadBalancer` resource as the primary access point for +> your service. + +Make sure the service is reachable at the domain name you added above, for +example `http://www.example.com`. The simplest way is to open a browser +and enter the name that you set up in DNS, and for which we just added the +ingress. + +You may also use a command line tool like `curl` to check the ingress. + +```bash +$ curl -kivL -H 'Host: www.example.com' 'http://203.0.113.2' +``` + +The options on this curl command will provide verbose output, following any +redirects, show the TLS headers in the output, and not error on insecure +certificates. With `ingress-nginx-controller`, the service will be available +with a TLS certificate, but it will be using a self-signed certificate +provided as a default from the `ingress-nginx-controller`. Browsers will show +a warning that this is an invalid certificate. This is expected and normal, +as we have not yet used cert-manager to get a fully trusted certificate +for our site. + +> *Warning*: It is critical to make sure that your ingress is available and +> responding correctly on the internet. This quick-start example uses Let's +> Encrypt to provide the certificates, which expects and validates both that the +> service is available and that during the process of issuing a certificate uses +> that validation as proof that the request for the domain belongs to someone +> with sufficient control over the domain. + +## Step 5 - Deploy cert-manager + +We need to install cert-manager to do the work with Kubernetes to request a +certificate and respond to the challenge to validate it. We can use Helm or +plain Kubernetes manifests to install cert-manager. + +Since we installed Helm earlier, we'll assume you want to use Helm; follow the +[Helm guide](../../installation/helm.md). For other methods, read the +[installation documentation](../../installation/README.md) for cert-manager. + +cert-manager mainly uses two different custom Kubernetes resources - known as +[`CRDs`](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) - +to configure and control how it operates, as well as to store state. These +resources are Issuers and Certificates. + +### Issuers + +An Issuer defines _how_ cert-manager will request TLS certificates. Issuers are +specific to a single namespace in Kubernetes, but there's also a `ClusterIssuer` +which is meant to be a cluster-wide version. + +Take care to ensure that your Issuers are created in the same namespace as the +certificates you want to create. You might need to add `-n my-namespace` to your +`kubectl create` commands. + +Your other option is to replace your `Issuers` with `ClusterIssuers`; +`ClusterIssuer` resources apply across all Ingress resources in your cluster. +If using a `ClusterIssuer`, remember to update the Ingress annotation `cert-manager.io/issuer` to +`cert-manager.io/cluster-issuer`. + +If you see issues with issuers, follow the [Troubleshooting Issuing ACME Certificates](../../troubleshooting/acme.md) guide. + +More information on the differences between `Issuers` and `ClusterIssuers` - including +when you might choose to use each can be found on [Issuer concepts](../../concepts/issuer.md#namespaces). + +### Certificates + +Certificates resources allow you to specify the details of the certificate you +want to request. They reference an issuer to define _how_ they'll be issued. + +For more information, see [Certificate concepts](../../concepts/certificate.md). + +## Step 6 - Configure a Let's Encrypt Issuer + +We'll set up two issuers for Let's Encrypt in this example: staging and production. + +The Let's Encrypt production issuer has [very strict rate limits](https://letsencrypt.org/docs/rate-limits/). +When you're experimenting and learning, it can be very easy to hit those limits. Because of that risk, +we'll start with the Let's Encrypt staging issuer, and once we're happy that it's working +we'll switch to the production issuer. + +Note that you'll see a warning about untrusted certificates from the staging issuer, but that's totally expected. + +Create this definition locally and update the email address to your own. This +email is required by Let's Encrypt and used to notify you of certificate +expiration and updates. + +```yaml file=./example/staging-issuer.yaml +``` + +Once edited, apply the custom resource: + +```bash +kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/staging-issuer.yaml +# expected output: issuer.cert-manager.io "letsencrypt-staging" created +``` + +Also create a production issuer and deploy it. As with the staging issuer, you +will need to update this example and add in your own email address. + +```yaml file=./example/production-issuer.yaml +``` + +```bash +kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/production-issuer.yaml +# expected output: issuer.cert-manager.io "letsencrypt-prod" created +``` + +Both of these issuers are configured to use the [`HTTP01`](../../configuration/acme/http01/README.md) challenge provider. + +Check on the status of the issuer after you create it: + +```bash +$ kubectl describe issuer letsencrypt-staging +Name: letsencrypt-staging +Namespace: default +Labels: +Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"cert-manager.io/v1","kind":"Issuer","metadata":{"annotations":{},"name":"letsencrypt-staging","namespace":"default"},(...)} +API Version: cert-manager.io/v1 +Kind: Issuer +Metadata: + Cluster Name: + Creation Timestamp: 2018-11-17T18:03:54Z + Generation: 0 + Resource Version: 9092 + Self Link: /apis/cert-manager.io/v1/namespaces/default/issuers/letsencrypt-staging + UID: 25b7ae77-ea93-11e8-82f8-42010a8a00b5 +Spec: + Acme: + Email: email@example.com + Private Key Secret Ref: + Key: + Name: letsencrypt-staging + Server: https://acme-staging-v02.api.letsencrypt.org/directory + Solvers: + Http 01: + Ingress: + Class: nginx +Status: + Acme: + Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/7374163 + Conditions: + Last Transition Time: 2018-11-17T18:04:00Z + Message: The ACME account was registered with the ACME server + Reason: ACMEAccountRegistered + Status: True + Type: Ready +Events: +``` + +You should see the issuer listed with a registered account. + +## Step 7 - Deploy a TLS Ingress Resource + +With all the prerequisite configuration in place, we can now do the pieces to +request the TLS certificate. There are two primary ways to do this: using +annotations on the ingress with [`ingress-shim`](../../usage/ingress.md) or +directly creating a certificate resource. + +In this example, we will add annotations to the ingress, and take advantage +of ingress-shim to have it create the certificate resource on our behalf. +After creating a certificate, the cert-manager will update or create a ingress +resource and use that to validate the domain. Once verified and issued, +cert-manager will create or update the secret defined in the certificate. + +> Note: The secret that is used in the ingress should match the secret defined +> in the certificate. There isn't any explicit checking, so a typo will result +> in the `ingress-nginx-controller` falling back to its self-signed certificate. +> In our example, we are using annotations on the ingress (and ingress-shim) +> which will create the correct secrets on your behalf. + +Edit the ingress add the annotations that were commented out in our earlier +example: + +```yaml file=./example/ingress-tls.yaml +``` + +and apply it: + +```bash +kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/ingress-tls.yaml +# expected output: ingress.networking.k8s.io/kuard configured +``` + +Cert-manager will read these annotations and use them to create a certificate, +which you can request and see: + +```bash +$ kubectl get certificate +NAME READY SECRET AGE +quickstart-example-tls True quickstart-example-tls 16m +``` + +cert-manager reflects the state of the process for every request in the +certificate object. You can view this information using the +`kubectl describe` command: + +```bash +$ kubectl describe certificate quickstart-example-tls +Name: quickstart-example-tls +Namespace: default +Labels: +Annotations: +API Version: cert-manager.io/v1 +Kind: Certificate +Metadata: + Cluster Name: + Creation Timestamp: 2018-11-17T17:58:37Z + Generation: 0 + Owner References: + API Version: networking.k8s.io/v1 + Block Owner Deletion: true + Controller: true + Kind: Ingress + Name: kuard + UID: a3e9f935-ea87-11e8-82f8-42010a8a00b5 + Resource Version: 9295 + Self Link: /apis/cert-manager.io/v1/namespaces/default/certificates/quickstart-example-tls + UID: 68d43400-ea92-11e8-82f8-42010a8a00b5 +Spec: + Dns Names: + www.example.com + Issuer Ref: + Kind: Issuer + Name: letsencrypt-staging + Secret Name: quickstart-example-tls +Status: + Acme: + Order: + URL: https://acme-staging-v02.api.letsencrypt.org/acme/order/7374163/13665676 + Conditions: + Last Transition Time: 2018-11-17T18:05:57Z + Message: Certificate issued successfully + Reason: CertIssued + Status: True + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CreateOrder 9m cert-manager Created new ACME order, attempting validation... + Normal DomainVerified 8m cert-manager Domain "www.example.com" verified with "http-01" validation + Normal IssueCert 8m cert-manager Issuing certificate... + Normal CertObtained 7m cert-manager Obtained certificate from ACME server + Normal CertIssued 7m cert-manager Certificate issued Successfully +``` + +The events associated with this resource and listed at the bottom +of the `describe` results show the state of the request. In the above +example the certificate was validated and issued within a couple of minutes. + +Once complete, cert-manager will have created a secret with the details of +the certificate based on the secret used in the ingress resource. You can +use the describe command as well to see some details: + +```bash +$ kubectl describe secret quickstart-example-tls +Name: quickstart-example-tls +Namespace: default +Labels: cert-manager.io/certificate-name=quickstart-example-tls +Annotations: cert-manager.io/alt-names=www.example.com + cert-manager.io/common-name=www.example.com + cert-manager.io/issuer-kind=Issuer + cert-manager.io/issuer-name=letsencrypt-staging + +Type: kubernetes.io/tls + +Data +==== +tls.crt: 3566 bytes +tls.key: 1675 bytes +``` + +Now that we have confidence that everything is configured correctly, you +can update the annotations in the ingress to specify the production issuer: + +```yaml file=./example/ingress-tls-final.yaml +``` + +```bash +$ kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/ingress-tls-final.yaml +ingress.networking.k8s.io/kuard configured +``` + +You will also need to delete the existing secret, which cert-manager is watching +and will cause it to reprocess the request with the updated issuer. + +```bash +$ kubectl delete secret quickstart-example-tls +secret "quickstart-example-tls" deleted +``` + +This will start the process to get a new certificate, and using describe +you can see the status. Once the production certificate has been updated, +you should see the example KUARD running at your domain with a signed TLS +certificate. + +```bash +$ kubectl describe certificate quickstart-example-tls +Name: quickstart-example-tls +Namespace: default +Labels: +Annotations: +API Version: cert-manager.io/v1 +Kind: Certificate +Metadata: + Cluster Name: + Creation Timestamp: 2018-11-17T18:36:48Z + Generation: 0 + Owner References: + API Version: networking.k8s.io/v1 + Block Owner Deletion: true + Controller: true + Kind: Ingress + Name: kuard + UID: a3e9f935-ea87-11e8-82f8-42010a8a00b5 + Resource Version: 283686 + Self Link: /apis/cert-manager.io/v1/namespaces/default/certificates/quickstart-example-tls + UID: bdd93b32-ea97-11e8-82f8-42010a8a00b5 +Spec: + Dns Names: + www.example.com + Issuer Ref: + Kind: Issuer + Name: letsencrypt-prod + Secret Name: quickstart-example-tls +Status: + Conditions: + Last Transition Time: 2019-01-09T13:52:05Z + Message: Certificate does not exist + Reason: NotFound + Status: False + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Generated 18s cert-manager Generated new private key + Normal OrderCreated 18s cert-manager Created Order resource "quickstart-example-tls-889745041" +``` + +You can see the current state of the ACME Order by running `kubectl describe` +on the Order resource that cert-manager has created for your Certificate: + +```bash +$ kubectl describe order quickstart-example-tls-889745041 +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Created 90s cert-manager Created Challenge resource "quickstart-example-tls-889745041-0" for domain "www.example.com" +``` + +Here, we can see that cert-manager has created 1 'Challenge' resource to fulfill +the Order. You can dig into the state of the current ACME challenge by running +`kubectl describe` on the automatically created Challenge resource: + +```bash +$ kubectl describe challenge quickstart-example-tls-889745041-0 +... +Status: + Presented: true + Processing: true + Reason: Waiting for http-01 challenge propagation + State: pending +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Started 15s cert-manager Challenge scheduled for processing + Normal Presented 14s cert-manager Presented challenge using http-01 challenge mechanism +``` + +From above, we can see that the challenge has been 'presented' and cert-manager +is waiting for the challenge record to propagate to the ingress controller. +You should keep an eye out for new events on the challenge resource, as a +'success' event should be printed after a minute or so (depending on how fast +your ingress controller is at updating rules): + +```bash +$ kubectl describe challenge quickstart-example-tls-889745041-0 +... +Status: + Presented: false + Processing: false + Reason: Successfully authorized domain + State: valid +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Started 71s cert-manager Challenge scheduled for processing + Normal Presented 70s cert-manager Presented challenge using http-01 challenge mechanism + Normal DomainVerified 2s cert-manager Domain "www.example.com" verified with "http-01" validation +``` + +> Note: If your challenges are not becoming 'valid' and remain in the 'pending' +> state (or enter into a 'failed' state), it is likely there is some kind of +> configuration error. Read the [Challenge resource reference +> docs](../../reference/api-docs.md#acme.cert-manager.io/v1.Challenge) for more +> information on debugging failing challenges. + +Once the challenge(s) have been completed, their corresponding challenge +resources will be *deleted*, and the 'Order' will be updated to reflect the +new state of the Order: + +```bash +$ kubectl describe order quickstart-example-tls-889745041 +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Created 90s cert-manager Created Challenge resource "quickstart-example-tls-889745041-0" for domain "www.example.com" + Normal OrderValid 16s cert-manager Order completed successfully +``` + +Finally, the 'Certificate' resource will be updated to reflect the state of the +issuance process. If all is well, you should be able to 'describe' the Certificate +and see something like the below: + +```bash +$ kubectl describe certificate quickstart-example-tls +Status: + Conditions: + Last Transition Time: 2019-01-09T13:57:52Z + Message: Certificate is up to date and has not expired + Reason: Ready + Status: True + Type: Ready + Not After: 2019-04-09T12:57:50Z +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Generated 11m cert-manager Generated new private key + Normal OrderCreated 11m cert-manager Created Order resource "quickstart-example-tls-889745041" + Normal OrderComplete 10m cert-manager Order "quickstart-example-tls-889745041" completed successfully +``` diff --git a/content/v1.12-docs/tutorials/acme/pomerium-ingress.md b/content/v1.12-docs/tutorials/acme/pomerium-ingress.md new file mode 100644 index 0000000000..90447f66af --- /dev/null +++ b/content/v1.12-docs/tutorials/acme/pomerium-ingress.md @@ -0,0 +1,191 @@ +--- +title: Pomerium Ingress +description: 'cert-manager tutorials: Solving ACME HTTP-01 challenges using Pomerium ingress' +--- + +This tutorial covers installing the [Pomerium Ingress Controller](https://pomerium.com/docs/k8s/ingress.html) and securing it with cert-manager. [Pomerium](https://pomerium.com) is an identity-aware proxy that can also provide a custom ingress controller for your Kubernetes services. + +## Prerequisites + +1. Install [Kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) and set the context to the cluster you'll be working with. + +1. Pomerium connects to an identity provider (**IdP**) to authenticate users. See one of their [guides](https://www.pomerium.com/docs/identity-providers/) to learn how to set up your IdP of choice to provide oauth2 validation. + +1. This tutorial assumes you have a domain space reserved for this cluster (such as `*.example.com`). You will need access to DNS for this domain to assign A and CNAME records as needed. + +## Install The Pomerium Ingress Controller + +1. Install Pomerium to your cluster: + + ```sh + kubectl apply -f https://raw.githubusercontent.com/pomerium/ingress-controller/main/deployment.yaml + ``` + + Define a Secret with your IdP configuration. See Pomerium's [Identity Providers](https://www.pomerium.com/docs/identity-providers) pages for more information specific to your IdP: + + ```yaml + apiVersion: v1 + kind: Secret + metadata: + name: idp + namespace: pomerium + type: Opaque + stringData: + client_id: ${IDP_PROVIDED_CLIENT_ID} + client_secret: ${IDP_PROVIDED_CLIENT_SECRET} + ``` + + Add the secret to the cluster with `kubectl apply -f`. + +1. Define the global settings for Pomerium: + + ```yaml + apiVersion: ingress.pomerium.io/v1 + kind: Pomerium + metadata: + name: global + namespace: pomerium + spec: + secrets: pomerium/bootstrap + authenticate: + url: https://authenticate.example.com + identityProvider: + provider: ${YOUR_IdP} + secret: pomerium/idp + # certificates: + # - pomerium/pomerium-proxy-tls + ``` + + Replace `${YOUR_IdP}` with your identity provider. Apply with `kubectl -f`. + + Note that the last two lines are commented out. They reference a TLS certificate we will create further in the process. + +## Install cert-manager + +Install cert-manager using any of the methods documented in the [Installation](https://cert-manager.io/docs/installation/) section of the cert-manager docs. The simplest method is to download and apply the provided manifest: + +```sh +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.10.1/cert-manager.yaml +``` + +## Configure Let's Encrypt Issuer + +For communication between the Ingresses and the internet, we'll want to use certificates signed by a trusted certificate authority like Let's Encrypt. This example creates two Let's Encrypt issuers, one for staging and one for production. + +The Let's Encrypt production issuer has [strict rate limits](https://letsencrypt.org/docs/rate-limits/). Before your configuration is finalized you may have to recreate services several times, hitting those limits. It's easy to confuse rate limiting with errors in configuration or operation while building your stack. + +Because of this, we will start with the Let's Encrypt staging issuer. Once your configuration is all but finalized, we will switch to a production issuer. Both of these issuers are configured to use the [`HTTP01`](../../configuration/acme/http01/README.md) challenge provider. + +1. The following YAML defines a staging certificate issuer. You must update the email address to your own. The `email` field is required by Let's Encrypt and used to notify you of certificate expiration and updates. + + ```yaml file=./example/pomerium-staging-issuer.yaml + ``` + + You can download and edit the example and apply it with `kubectl apply -f`, or edit, and apply the custom resource in one command: + + ```bash + kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/pomerium-staging-issuer.yaml + ``` + +1. Create a production issuer and deploy it. As with the staging issuer, update this example with your own email address: + + ```yaml file=./example/pomerium-production-issuer.yaml + ``` + + ```bash + kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/pomerium-production-issuer.yaml + ``` + +1. You can confirm on the status of the issuers after you create them: + + ```bash + kubectl describe issuer -n pomerium letsencrypt-staging + kubectl describe issuer -n pomerium letsencrypt-prod + ``` + + You should see the issuer listed with a registered account. + +1. Define a certificate for the Pomerium Proxy service. This should be the only certificate you need to manually define: + + ```yaml + apiVersion: cert-manager.io/v1 + kind: Certificate + metadata: + name: pomerium-proxy-tls + namespace: pomerium + spec: + dnsNames: + - 'authenticate.example.com' + issuerRef: + kind: Issuer + name: letsencrypt-staging + secretName: pomerium-proxy-tls + ``` + + Adjust the `dnsNames` value to match your domain space. The subdomain (`authenticate` in our example) must match the domain used for the callback URL in your IdP configuration. Add the certificate with `kubectl -f`. + +1. Uncomment the last two lines of the Pomerium global configuration that reference your newly created certificate, and re-apply to the cluster. + +Pomerium should now be installed and running in your cluster. You can verify by going to `https://authenticate.example.com` in your browser. Use `kubectl describe pomerium` to review the status of the Pomerium deployment and see recent events. + +## Define a Test Service + +To test our new Ingress Controller, we will add the [kuard](https://github.com/kubernetes-up-and-running/kuard) app to our cluster and define an Ingress for it. + +1. Define the kuard deployment and associated service: + + ```yaml file=./example/deployment.yaml + ``` + + ```yaml file=./example/service.yaml + ``` + + You can download and reference these files locally, or you can reference them from the GitHub source repository for this documentation. + + To install the example service from the tutorial files straight from GitHub: + + ```bash + kubectl apply -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/deployment.yaml + kubectl apply -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/service.yaml + ``` + +1. Create a new Ingress manifest (`example-ingress.yaml`) for our test service: + + ```yaml + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: kuard + annotations: + cert-manager.io/issuer: letsencrypt-staging + ingress.pomerium.io/policy: '[{"allow":{"and":[{"domain":{"is":"example.com"}}]}}]' + spec: + ingressClassName: pomerium + rules: + - host: kuard.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kuard + port: + number: 80 + tls: + - hosts: + - kuard.example.com + secretName: kuard.example.com-tls + ``` + + Again, change the references to `example.com` to match your domain space. + +1. Apply the Ingress manifest to the cluster: + + ```bash + kubectl apply -f example-ingress.yaml + ``` + +The Pomerium Ingress Controller will use cert-manager to automatically provision a certificate from the `letsencrypt-staging` issuer for the route to `kuard.example.com`. + +Once you've configured all your application services correctly in the cluster, adjust the issuer for your Ingresses (including the Authenticate service) to use `letsencrypt-prod`. diff --git a/content/v1.12-docs/tutorials/backup.md b/content/v1.12-docs/tutorials/backup.md new file mode 100644 index 0000000000..8a1f9f69fa --- /dev/null +++ b/content/v1.12-docs/tutorials/backup.md @@ -0,0 +1,167 @@ +--- +title: Backup and Restore Resources +description: 'cert-manager tutorials: Backing up your cert-manager installation' +--- + +If you need to uninstall cert-manager, or transfer your installation to a new +cluster, you can backup all of cert-manager's configuration in order to later +re-install. + +## Backing up cert-manager resource configuration + +The following commands will back up the configuration of `cert-manager` +resources. Doing that might be useful before upgrading `cert-manager`. As +this backup does not include the `Secrets` containing the X.509 +certificates, restoring to a cluster that does not already have those +`Secret` objects will result in the certificates being reissued. + +### Backup + +To backup all of your cert-manager configuration resources, run: + +```bash +kubectl get --all-namespaces -oyaml issuer,clusterissuer,cert > backup.yaml +``` + +If you are transferring data to a new cluster, you may also need to copy across +additional `Secret` resources that are referenced by your configured Issuers, such +as: + +#### CA Issuers + +- The root CA `Secret` referenced by `issuer.spec.ca.secretName` + +#### Vault Issuers + +- The token authentication `Secret` referenced by + `issuer.spec.vault.auth.tokenSecretRef` +- The AppRole configuration `Secret` referenced by + `issuer.spec.vault.auth.appRole.secretRef` + +#### ACME Issuers + +- The ACME account private key `Secret` referenced by `issuer.acme.privateKeySecretRef` +- Any `Secret`s referenced by DNS providers configured under the + `issuer.acme.dns01.providers` and `issuer.acme.solvers.dns01` fields. + +### Restore + +In order to restore your configuration, you can `kubectl apply` the files +created above after installing cert-manager, with the exception of the +`uid` and `resourceVersion` fields that do not need to be restored: + +```bash +kubectl apply -f <(awk '!/^ *(resourceVersion|uid): [^ ]+$/' backup.yaml) +``` + +## Full cluster backup and restore + +This section refers to backing up and restoring 'all' Kubernetes resources in a +cluster — including some `cert-manager` ones — for scenarios such as disaster +recovery, cluster migration etc. + +*Note*: We have tested this process on simple Kubernetes test clusters with a limited set of Kubernetes releases. To avoid data loss, please test both the backup and the restore strategy on your own cluster before depending upon it in production. If you encounter any errors, please open a GitHub issue or a PR to document variations on this process for different Kubernetes environments. + +### Avoiding unnecessary certificate reissuance + +#### Order of restore + +If `cert-manager` does not find a Kubernetes `Secret` with an X.509 certificate +for a `Certificate`, reissuance will be triggered. To avoid unnecessary +reissuance after a restore, ensure that `Secret`s are restored before +`Certificate`s. Similarly, `Secret`s should be restored before `Ingress`es if you +are using [`ingress-shim`](../usage/ingress.md). + +#### Excluding some cert-manager resources from backup + +`cert-manager` has a number of custom resources that are designed to represent a +point-in-time operation. An example would be a `CertificateRequest` that +represents a one-time request for an X.509 certificate. The status of these +resources can depend on other ephemeral resources (such as a temporary `Secret` +holding a private key) so `cert-manager` might not be able to correctly recreate +the state of these resources at a later point. + +In most cases backup and restore tools will not restore the statuses of custom resources, +so including such one-time resources in a backup can result in an unnecessary reissuance +after a restore as without the status fields `cert-manager` will not be able to tell that, +for example, an `Order` has already been fulfilled. +To avoid unnecessary reissuance, we recommend that `Order`s and `Challenge`s are excluded +from the backup. We also don't recommend backing up `CertificateRequest`s, see [Backing up CertificateRequests](#backing-up-certificaterequests) + +### Restoring Ingress Certificates + +A `Certificate` created for an `Ingress` via [`ingress-shim`](../usage/ingress.md) will have an [owner +reference](https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#owners-and-dependents) +pointing to the `Ingress` resource. `cert-manager` uses the owner reference to +verify that the `Certificate` 'belongs' to that `Ingress` and will not attempt to +create/correct it for an existing `Certificate`. After a full +cluster recreation, a restored owner reference would probably be incorrect +(`Ingress` UUID will have changed). The incorrect owner reference could lead +to a situation where updates to the `Ingress` (i.e a new DNS name) are not +applied to the `Certificate`. + +To avoid this issue, in most cases `Certificate`s created via `ingress-shim` +can be excluded from the backup. Given that the restore happens +in the correct order (`Secret` with the X.509 certificate restored before +the `Ingress`) `cert-manager` will be able to create a new `Certificate` +for the `Ingress` and determine that the existing `Secret` is for that `Certificate`. + +### Velero + +We have briefly tested backup and restore with `velero` `v1.5.3` and +`cert-manager` versions `v1.3.1` and `v1.3.0` as well as `velero` `v1.3.1` + and `cert-manager` `v1.1.0`. + + A few potential edge cases: + +- Ensure that the backups include `cert-manager` CRDs. + For example, we have seen that if `--exclude-namespaces` flag is passed to + `velero backup create`, CRDs for which there are no actual resources to be + included in the backup might also not be included in backup unless + `--include-cluster-resources=true` flag is also passed to the backup command. + +- Velero does not restore statuses of custom resources, so you should probably + exclude `Order`s, `Challenge`s and `CertificateRequest`s from the backup, see + [Excluding some cert-manager resources from backup](#excluding-some-cert-manager-resources-from-backup). + +- Velero's [default restore order](https://github.com/vmware-tanzu/velero/blob/main/pkg/cmd/server/server.go#L470)(`Secrets` before `Ingress`es, Custom Resources + restored last), should ensure that there is no unnecessary certificate reissuance + due to the order of restore operation, see [Order of restore](#order-of-restore). + +- When restoring the deployment of `cert-manager` itself, it may be necessary to + restore `cert-manager`'s RBAC resources before the rest of the deployment. + This is because `cert-manager`'s controller needs to be able to create + `Certificate`'s for the `cert-manager`'s webhook before the webhook can become + ready. In order to do this, the controller needs the right permissions. Since + Velero by default restores pods before RBAC resources, the restore might get + stuck waiting for the webhook pod to become ready. + +- Velero does not restore owner references, so it may be necessary to exclude + `Certificate`s created for `Ingress`es from the backup even when not + re-creating the `Ingress` itself. See [Restoring Ingress Certificates](#restoring-ingress-certificates). + +## Backing up CertificateRequests + + We no longer recommend including `CertificateRequest` resources in a backup + for most scenarios. + `CertificateRequest`s are designed to represent a one-time + request for an X.509 certificate. Once the request has been fulfilled, + `CertificateRequest` can usually be safely deleted[^1]. In most cases (such as when + a `CertificateRequest` has been created for a `Certificate`) a new + `CertificateRequest` will be created when needed (i.e at a time of a renewal + of a `Certificate`). + In `v1.3.0` , as part of our work towards [policy + implementation](https://github.com/cert-manager/cert-manager/pull/3727) we + introduced identity fields for `CertificateRequest` resources where, at a time + of creation, `cert-mananager`'s webhook updates `CertificateRequest`'s spec + with immutable identity fields, representing the identity of the creator of + the `CertificateRequest`. + This introduces some extra complexity for backing up + and restoring `CertificateRequest`s as the identity of the restorer might + differ from that of the original creator and in most cases a restored + `CertificateRequest` would likely end up with incorrect state. + + [^1]: there is an edge case where certain changes to `Certificate` spec may not + trigger re-issuance if there is no `CertificateRequest` for that + `Certificate`. See [documentation on when do certificates get + re-issued](../faq/README.md#when-do-certs-get-re-issued). \ No newline at end of file diff --git a/content/v1.12-docs/tutorials/getting-started-aks-letsencrypt/README.md b/content/v1.12-docs/tutorials/getting-started-aks-letsencrypt/README.md new file mode 100644 index 0000000000..f712fa6b7a --- /dev/null +++ b/content/v1.12-docs/tutorials/getting-started-aks-letsencrypt/README.md @@ -0,0 +1,687 @@ +--- +title: Deploy cert-manager on Azure Kubernetes Service (AKS) and use Let's Encrypt to sign a certificate for an HTTPS website +description: | + Learn how to deploy cert-manager on Azure Kubernetes Service (AKS) + and configure it to get a signed certificate from Let's Encrypt for an HTTPS web server, + using the DNS-01 protocol and Azure DNS with workload identity federation. +--- + +*Last Verified: 10 January 2023* + +In this tutorial you will learn how to deploy and configure cert-manager on Azure Kubernetes Service (AKS) +and how to deploy an HTTPS web server and make it available on the Internet. +You will learn how to configure cert-manager to get a signed certificate from Let's Encrypt, +which will allow clients to connect to your HTTPS website securely. +You will configure cert-manager to use the [Let's Encrypt DNS-01 challenge protocol](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) with Azure DNS, +using workload identity federation to authenticate to Azure. + +> **Microsoft Azure**: A suite of cloud computing services by Microsoft.
      +> **Kubernetes**: Runs on your servers. Automates the deployment, scaling, and management of containerized applications.
      +> **cert-manager**: Runs in Kubernetes. Obtains TLS / SSL certificates and ensures the certificates are valid and up-to-date.
      +> **Let’s Encrypt**: An Internet service. Allows you to generate free short-lived SSL certificates. + +# Part 1 + +In the first part of this tutorial you will learn the basics required to deploy an HTTPS website on an Azure Kubernetes cluster using cert-manager to create the SSL certificate for the web server. +You will create a DNS domain for your website, create an Azure Kubernetes cluster, install cert-manager, create an SSL certificate and then deploy a web server which responds to HTTPS requests from clients on the Internet. +But the SSL certificate in part 1 is only for testing purposes. + +In part 2 you will learn how to configure cert-manager to use Let's Encrypt and Azure DNS to create a trusted SSL certificate which you can use in production. + +## Configure the Azure CLI (`az`) + +If your have not already done so, [download and install the Azure CLI (`az`)](https://learn.microsoft.com/en-us/cli/azure/). + +Set up the `az` command for interactive use: + +```bash +az init +``` + +Log in, if you have not already done so: + +```bash +az login +``` + +Set the default resource group and location: + +```bash +export AZURE_DEFAULTS_GROUP=your-resource-group # ❗ Your Azure resource group +export AZURE_DEFAULTS_LOCATION=eastus2 # ❗ Your Azure location. +``` + +> ℹ️ You will need an `az` version `>=2.40.0`. Run `az version` to print the current version. +> +> ℹ️ When you run `az init`, choose "Optimize for interaction" when prompted. +> +> ℹ️ When you run `az login`, a web browser will be opened at https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize. +> Continue the login in the web browser and then return to your terminal. +>> +> 📖 Read the [Azure Command-Line Interface (CLI) documentation](https://learn.microsoft.com/en-us/cli/azure/). +> +> 📖 Read [CLI configuration values and environment variables](https://learn.microsoft.com/en-us/cli/azure/azure-cli-configuration?source=recommendations#cli-configuration-values-and-environment-variables) for more ways to configure the `az` defaults. + +## Create a public domain name + +In this tutorial you will deploy an HTTPS website with a publicly accessible domain name, so you will need to register a domain unless you already have one. +You could use any [domain name registrar](https://www.cloudflare.com/en-gb/learning/dns/glossary/what-is-a-domain-name-registrar/) to register a domain name for your site. +Here we will use a registrar called `Gandi` and register a cheap domain name for the purposes of this tutorial. +We will use the domain name: `cert-manager-tutorial-22.site` but you should choose your own. + +Now that you know your domain name, save it in an environment variable: + +```bash +export DOMAIN_NAME=cert-manager-tutorial-22.site # ❗ Replace this with your own DNS domain name +``` + +And add it to Azure DNS as a zone: + +```bash +az network dns zone create --name $DOMAIN_NAME +``` + +Log in to the control panel for your domain registrar and set the NS records for your domain to match the DNS names of the Azure [authoritative DNS servers](https://www.cloudflare.com/en-gb/learning/dns/dns-server-types/). +You can find these by looking for the NS records of your Azure hosted DNS zone: + +```bash +az network dns zone show --name $DOMAIN_NAME --output yaml +``` + +You can check that the NS records have been updated using `dig` to "trace" the hierarchy of NS records, +rather than using your local DNS resolver: + +```bash +dig $DOMAIN_NAME ns +trace +nodnssec +``` + +> ⏲ It **may** take more than 1 hour for the NS records to be updated in the parent zone, +> and it may take some time for the old NS records to be replaced in the caches of DNS resolver servers, +> if you looked up the DNS name before updating the NS records. +> +> 📖 Read [How do I Update My DNS Records?](https://docs.gandi.net/en/domain_names/common_operations/dns_records.html) in the `Gandi.net` docs, +> or seek the equivalent documentation for your own domain name registrar. + +## Create a Kubernetes cluster + +To get started, let's create a Kubernetes cluster in Microsoft Azure. +You will need to pick a name for your cluster. +Here, we will go with "test-cluster-1". +Save it in an environment variable: + +```bash +export CLUSTER=test-cluster-1 +``` + +Now, create the cluster using the following command: + +```bash +az aks create \ + --name ${CLUSTER} \ + --node-count 1 \ + --node-vm-size "Standard_B2s" \ + --load-balancer-sku basic +``` + +Update your `kubectl` config file with the credentials for your new cluster: + +```bash +az aks get-credentials --admin --name "$CLUSTER" +``` + +Now check that you can connect to the cluster: + +```bash +kubectl get nodes -o wide +``` + +> ⏲ It will take 4-5 minutes to create the cluster. +> +> 💵 To minimize your cloud bill, this command creates a 1-node cluster using a +> low cost virtual machine and load balancer. +> +> ⚠️ This cluster is only suitable for learning purposes it is not suitable for production use. +> +> 📖 Read [Run Kubernetes in Azure the Cheap Way](https://trstringer.com/cheap-kubernetes-in-azure/) for more cost saving tips. +> + +## Install cert-manager + +Now you can install and configure cert-manager. + +Install cert-manager using `helm` as follows: + +```bash +helm repo add jetstack https://charts.jetstack.io +helm repo update +helm upgrade cert-manager jetstack/cert-manager \ + --install \ + --create-namespace \ + --wait \ + --namespace cert-manager \ + --set installCRDs=true +``` + +This will create three Deployments and some Services and Pods in a new namespace called `cert-manager`. +It also installs various cluster scoped supporting resources such as RBAC roles and Custom Resource Definitions. + +You can view some of the resources that have been installed as follows: + +```bash +kubectl -n cert-manager get all +``` + +And you can explore the Custom Resource Definitions (cert-manager's API) using `kubectl explain`, as follows: + +```bash +kubectl explain Certificate +kubectl explain CertificateRequest +kubectl explain Issuer +``` + +> 📖 Read about [other ways to install cert-manager](../../installation/README.md). +> +> 📖 Read more about [Certificates and Issuers](../../concepts/README.md). + +## Create a test ClusterIssuer and a Certificate + +Now everything is ready for you to create your first certificate. +This will be a self-signed certificate but later we'll replace it with a Let's Encrypt signed certificate. + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/clusterissuer-selfsigned.yaml +``` +🔗 `clusterissuer-selfsigned.yaml` + +```bash +kubectl apply -f clusterissuer-selfsigned.yaml +``` + +Then use `envsubst` to substitute your chosen domain name into the following Certificate template: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/certificate.yaml +``` +🔗 `certificate.yaml` + +```bash +envsubst < certificate.yaml | kubectl apply -f - +``` + +> 🔗 If you don't already have `envsubst` installed you can [download and install a Go implementation of `envsubst`](https://github.com/a8m/envsubst). + +Use `cmctl status certificate` to check the status of the Certificate: + +```bash +cmctl status certificate www +``` + +If successful, the private key and the signed certificate will be stored in a Secret called `www-tls`. +You can use `cmctl inspect secret www-tls` to decode the base64 encoded X.509 content of the Secret: + +```terminal +$ cmctl inspect secret www-tls +... +Valid for: + DNS Names: + - www.cert-manager-tutorial-22.site + URIs: + IP Addresses: + Email Addresses: + Usages: + - digital signature + - key encipherment + - server auth +... +``` + +## Deploy a sample web server + +Now deploy a simple web server which responds to HTTPS requests with "hello world!". +The SSL / TLS key and certificate are supplied to the web server by using the `www-tls` Secret as a volume +and by mounting its contents into the file system of the `hello-app` container in the Pod: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/deployment.yaml +``` +🔗 `deployment.yaml` + +```bash +kubectl apply -f deployment.yaml +``` + +You also need to create a Kubernetes LoadBalancer Service, so that connections from the Internet can be routed to the web server Pod. +When you create the following Kubernetes Service, an Azure load balancer with an ephemeral public IP address will also be created: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/service.yaml +``` +🔗 `service.yaml` + +Create a unique DNS name for the LoadBalancer Service and then apply it: +```bash +export AZURE_LOADBALANCER_DNS_LABEL_NAME=lb-$(uuidgen) # ❗ The label must start with a lowercase ASCII letter +envsubst < service.yaml | kubectl apply -f - +``` + +Within 2-3 minutes, a load balancer should have been provisioned with a public IP. + +```bash +kubectl get service helloweb +``` + +Sample output + +```terminal +$ kubectl get service helloweb +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +helloweb LoadBalancer 10.0.141.1 20.114.151.62 443:30394/TCP 7m15s +``` + +The `EXTERNAL-IP` will be different for you and it may be different each time you re-create the LoadBalancer service, +but it will have a stable DNS host name associated with it +because you annotated the Service with `azure-dns-label-name`. +This stable DNS hostname can be used as an alias for your chosen `$DOMAIN_NAME` by creating a [DNS CNAME record](https://www.cloudflare.com/en-gb/learning/dns/dns-records/dns-cname-record/): + +```bash +az network dns record-set cname set-record \ + --zone-name $DOMAIN_NAME \ + --cname $AZURE_LOADBALANCER_DNS_LABEL_NAME.$AZURE_DEFAULTS_LOCATION.cloudapp.azure.com \ + --record-set-name www +``` + +Check that `www.$DOMAIN_NAME` now resolves to the ephemeral public IP address of the load balancer: + +```terminal +$ dig www.$DOMAIN_NAME A +... +;; QUESTION SECTION: +;www.cert-manager-tutorial-22.site. IN A +... +;; ANSWER SECTION: +www.cert-manager-tutorial-22.site. 3600 IN CNAME lb-ec8776e1-d067-4d4c-8cce-fdf07ce48260.eastus2.cloudapp.azure.com. +lb-ec8776e1-d067-4d4c-8cce-fdf07ce48260.eastus2.cloudapp.azure.com. 10 IN A 20.122.27.189 +... +``` + +If the DNS is correct and the load balancer is working and the hello world web server is running, +you should now be able to connect to it using curl or using your web browser: + +```bash +curl --insecure -v https://www.$DOMAIN_NAME +``` + +> ⚠️ We used curl's `--insecure` option because it rejects self-signed certificates by default. +> Later you will learn how to create a trusted certificate signed by Let's Encrypt. + +You should see that the certificate has the expected DNS names and that it is self-signed: + +```terminal +... +* Server certificate: +* subject: CN=www.cert-manager-tutorial-22.site +* start date: Jan 4 15:28:30 2023 GMT +* expire date: Apr 4 15:28:30 2023 GMT +* issuer: CN=www.cert-manager-tutorial-22.site +* SSL certificate verify result: self-signed certificate (18), continuing anyway. +... +Hello, world! +Protocol: HTTP/2.0! +Hostname: helloweb-55cb4cd887-tjlvh +``` + +> 📖 Read more about [Using a Service to Expose Your App](https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/). +> +> 📖 Read more about [Using a public IP address and DNS label with the Azure Kubernetes Service (AKS) load balancer](https://learn.microsoft.com/en-us/azure/aks/static-ip). + +# Part 2 + +In part 1 you created a test certificate. +Now you will learn how to configure cert-manager to use Let's Encrypt and Azure DNS to create a trusted certificate which you can use in production. +You need to prove to Let's Encrypt that you own the domain name of the certificate and one way to do this is to create a special DNS record in that domain. +This is known as the [DNS-01 challenge type](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge). + +cert-manager can create that DNS record for you in by using the Azure DNS API but it needs to authenticate to Azure first, +and currently the most secure method of authentication is to use [workload identity federation](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview). +The advantages of this method are that cert-manager will use an ephemeral Kubernetes ServiceAccount Token to authenticate to Azure and the token need not be stored in a Kubernetes Secret. + +> ℹ️ cert-manager `>= v1.11.0` supports workload identity federation for ACME (Let's Encrypt) DNS-01 with Azure DNS. +> Older versions of cert-manager support other authentication mechanisms which are not covered in this tutorial. +> +> 📖 Read about [other ways to configure the ACME issuer with Azure DNS](../../configuration/acme/dns01/azuredns.md). + +## Install the Azure workload identity features + +The workload identity features in Azure AKS are relatively new (at time of writing) and they require some non-default features to be enabled. + +Install the [Azure CLI AKS Preview Extension](https://github.com/Azure/azure-cli-extensions/tree/main/src/aks-preview), +which you will need to configure some advanced workload identity federation features on your AKS cluster. + +```bash +az extension add --name aks-preview +``` + +Register the `EnableWorkloadIdentityPreview` feature flag which is required for the AKS cluster in this demo. + +```bash +az feature register --namespace "Microsoft.ContainerService" --name "EnableWorkloadIdentityPreview" + +# It takes a few minutes for the status to show Registered. Verify the registration status by using the az feature list command: +az feature list -o table --query "[?contains(name, 'Microsoft.ContainerService/EnableWorkloadIdentityPreview')].{Name:name,State:properties.state}" + +# When ready, refresh the registration of the Microsoft.ContainerService resource provider by using the az provider register command: +az provider register --namespace Microsoft.ContainerService +``` + +> 📖 Read more about [Registering the `EnableWorkloadIdentityPreview` feature flag](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster). + +## Reconfigure the cluster + +Next enable the workload identity federation features on the cluster that you created earlier: + +```bash +az aks update \ + --name ${CLUSTER} \ + --enable-oidc-issuer \ + --enable-workload-identity # ℹ️ This option is currently only available when using the aks-preview extension. +``` + +> 📖 Read [Deploy and configure workload identity on an Azure Kubernetes Service (AKS) cluster](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster) for more information about the `--enable-workload-identity` feature. + +## Reconfigure cert-manager + +We will label the cert-manager controller Pod and ServiceAccount for the attention of the Azure Workload Identity webhook, +which will result in the cert-manager controller Pod having an extra volume containing a Kubernetes ServiceAccount token which it will use to authenticate with Azure. + +The labels can be configured using the Helm values file below: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/values.yaml +``` +🔗 `values.yaml` + +```bash +helm upgrade cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --reuse-values \ + --values values.yaml +``` + +The newly rolled out cert-manager Pod will have some new environment variables set, +and the Azure workload-identity ServiceAccount token as a projected volume: + +```bash +kubectl describe pod -n cert-manager -l app.kubernetes.io/component=controller +``` + +```terminal +Containers: + ... + cert-manager-controller: + ... + Environment: + ... + AZURE_CLIENT_ID: + AZURE_TENANT_ID: f99bd6a4-665c-41cf-aff1-87a89d5c62d4 + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + AZURE_AUTHORITY_HOST: https://login.microsoftonline.com/ + Mounts: + /var/run/secrets/azure/tokens from azure-identity-token (ro) +Volumes: + ... + azure-identity-token: + Type: Projected (a volume that contains injected data from multiple sources) + TokenExpirationSeconds: 3600 +``` + +> 📖 Read about [the role of the Mutating Admission Webhook](https://azure.github.io/azure-workload-identity/docs/installation/mutating-admission-webhook.html) in Azure AD Workload Identity for Kubernetes. +> +> 📖 Read about [other values that can be customized in the cert-manager Helm chart](https://artifacthub.io/packages/helm/cert-manager/cert-manager?modal=values). + +## Create an Azure Managed Identity + +When cert-manager creates a certificate using Let's Encrypt +it can use DNS records to prove that it controls the DNS domain names in the certificate. +In order for cert-manager to use the Azure API and manipulate the records in the Azure DNS zone, +it needs an Azure account and the best type of account to use is called a "Managed Identity". +This account does not come with a password or an API key and it is designed for use by machines rather than humans. + +Choose a managed identity name: + +```bash +export USER_ASSIGNED_IDENTITY_NAME=cert-manager-tutorials-1 # ❗ Replace with your preferred managed identity name +``` + +Create the Managed Identity: + +```bash +az identity create --name "${USER_ASSIGNED_IDENTITY_NAME}" +``` + +Grant it permission to modify the DNS zone records: + +```bash +export USER_ASSIGNED_IDENTITY_CLIENT_ID=$(az identity show --name "${USER_ASSIGNED_IDENTITY_NAME}" --query 'clientId' -o tsv) +az role assignment create \ + --role "DNS Zone Contributor" \ + --assignee $USER_ASSIGNED_IDENTITY_CLIENT_ID \ + --scope $(az network dns zone show --name $DOMAIN_NAME -o tsv --query id) +``` + +> 📖 Read [What are managed identities for Azure resources?](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) +> for an overview of managed identities and their uses. +> +> 📖 Read [Azure built-in roles](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) to learn about the "DNS Zone Contributor" role. + +## Add a federated identity + +Now we will configure Azure to trust certain Kubernetes ServiceAccount tokens, +in particular, the service account tokens from our specific Kubernetes cluster, +and only tokens which are associated with the cert-manager ServiceAccount. +cert-manager will authenticate to Azure using an short lived Kubernetes ServiceAccount token, +and it will be able to impersonate the managed identity that you created in the previous step. + +First export the following environment variables containing the name and namespace of the Kubernetes ServiceAccount used by the cert-manager controller: + +```bash +export SERVICE_ACCOUNT_NAME=cert-manager # ℹ️ This is the default Kubernetes ServiceAccount used by the cert-manager controller. +export SERVICE_ACCOUNT_NAMESPACE=cert-manager # ℹ️ This is the default namespace for cert-manager. +``` + +Then configure the managed identity to trust the cert-manager Kubernetes ServiceAccount, +by supplying its "subject" (the distinguishing name of the Kubernetes ServiceAccount) +and its "issuer" (a URL at which the JWT signing certificate and other metadata can be downloaded): + +```bash +export SERVICE_ACCOUNT_ISSUER=$(az aks show --resource-group $AZURE_DEFAULTS_GROUP --name $CLUSTER --query "oidcIssuerProfile.issuerUrl" -o tsv) +az identity federated-credential create \ + --name "cert-manager" \ + --identity-name "${USER_ASSIGNED_IDENTITY_NAME}" \ + --issuer "${SERVICE_ACCOUNT_ISSUER}" \ + --subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}" +``` + +> 📖 Read about [Workload identity federation](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation) in the Microsoft identity platform documentation. + + +## Create a ClusterIssuer for Let's Encrypt Staging + +A ClusterIssuer is a custom resource which tells cert-manager how to sign a Certificate. +In this case the ClusterIssuer will be configured to connect to the Let's Encrypt staging server, +which allows us to test everything without using up our Let's Encrypt certificate quota for the domain name. + +Save the following content to a file called `clusterissuer-lets-encrypt-staging.yaml`, change the `email` field to use your email address and apply it: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/clusterissuer-lets-encrypt-staging.yaml +``` +🔗 `clusterissuer-lets-encrypt-staging.yaml` + + +As you can see there are some variables in the `clusterissuer-lets-encrypt-staging.yaml` which need to be filled in before we apply it; +most have been defined earlier in this tutorial but you need to set the following: + +```bash +export EMAIL_ADDRESS= # ❗ Replace this with your email address +export AZURE_SUBSCRIPTION= # ❗ Replace this with your Azure account name +``` + +Now use `envsubst` to fill in the variables and pipe it into `kubectl apply`, as follows: + +```bash +export AZURE_SUBSCRIPTION_ID=$(az account show --name $AZURE_SUBSCRIPTION --query 'id' -o tsv) +envsubst < clusterissuer-lets-encrypt-staging.yaml | kubectl apply -f - +``` + +You can check the status of the ClusterIssuer: + +```bash +kubectl describe clusterissuer letsencrypt-staging +``` + +Example output + +```console +Status: + Acme: + Last Registered Email: firstname.lastname@example.com + Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/77882854 + Conditions: + Last Transition Time: 2022-11-29T13:05:33Z + Message: The ACME account was registered with the ACME server + Observed Generation: 1 + Reason: ACMEAccountRegistered + Status: True + Type: Ready +``` + +> ℹ️ Let's Encrypt uses the Automatic Certificate Management Environment (ACME) protocol +> which is why the configuration above is under a key called `acme`. +> +> ℹ️ The email address is only used by Let's Encrypt to remind you to renew the certificate after 30 days before expiry. You will only receive this email if something goes wrong when renewing the certificate with cert-manager. +> +> ℹ️ The Let's Encrypt production issuer has [very strict rate limits](https://letsencrypt.org/docs/rate-limits/). +> When you're experimenting and learning, it can be very easy to hit those limits. Because of that risk, +> we'll start with the Let's Encrypt staging issuer, and once we're happy that it's working +> we'll switch to the production issuer. +> +> 📖 Read more about [configuring the ACME Issuer](../../configuration/acme/README.md). +> + + +## Re-issue the Certificate using Let's Encrypt + +Patch the Certificate to use the staging ClusterIssuer: + +```bash +kubectl patch certificate www --type merge -p '{"spec":{"issuerRef":{"name":"letsencrypt-staging"}}}' +``` + +That should trigger cert-manager to renew the certificate: +Use `cmctl` to check: + +```bash +cmctl status certificate www +cmctl inspect secret www-tls +``` + +And finally, when the new certificate has been issued, you must restart the web server to use it: + +```bash +kubectl rollout restart deployment helloweb +``` + +You should once again be able to connect to the website, but this time you will see the Let's Encrypt staging certificate: + +```terminal +$ curl -v --insecure https://www.$DOMAIN_NAME +... +* Server certificate: +* subject: CN=www.cert-manager-tutorial-22.site +* start date: Jan 5 12:41:14 2023 GMT +* expire date: Apr 5 12:41:13 2023 GMT +* issuer: C=US; O=(STAGING) Let's Encrypt; CN=(STAGING) Artificial Apricot R3 +* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. +... +Hello, world! +Protocol: HTTP/2.0! +Hostname: helloweb-9b8bcdd56-6rxm8 +``` + +> ⚠️ We used curl's `--insecure` option again here because the Let's Encrypt staging issuer creates untrusted certificates. +> Next you will learn how to create a trusted certificate signed by the Let's Encrypt production issuer. + +## Create a production ready certificate + +Now that everything is working with the Let's Encrypt staging server, we can switch to the production server and get a trusted certificate. + +Create a Let's Encrypt production Issuer by copying the staging ClusterIssuer YAML and modifying the server URL and the names, +then apply it: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/clusterissuer-lets-encrypt-production.yaml +``` +🔗 `clusterissuer-lets-encrypt-production.yaml` + + +```bash +envsubst < clusterissuer-lets-encrypt-production.yaml | kubectl apply -f - +``` + +Check the status of the ClusterIssuer: + +```bash +kubectl describe clusterissuer letsencrypt-production +``` + +Patch the Certificate to use the production ClusterIssuer: + +```bash +kubectl patch certificate www --type merge -p '{"spec":{"issuerRef":{"name":"letsencrypt-production"}}}' +``` + +That should trigger cert-manager to renew the certificate: +Use `cmctl` to check: + +```bash +cmctl status certificate www +cmctl inspect secret www-tls +``` + +And finally, when the new certificate has been issued, you must restart the web server to use it: + +```bash +kubectl rollout restart deployment helloweb +``` + +Now you should be able to connect to the web server securely, without the `--insecure` flag, +and if you visit the site in your web browser, it should show a padlock (🔒) symbol next to the URL. + +```bash +curl -v https://www.$DOMAIN_NAME +``` + +```terminal +... +* Server certificate: +* subject: CN=cert-manager-tutorial-22.site +* start date: Nov 30 15:41:40 2022 GMT +* expire date: Feb 28 15:41:39 2023 GMT +* subjectAltName: host "www.cert-manager-tutorial-22.site" matched cert's "www.cert-manager-tutorial-22.site" +* issuer: C=US; O=Let's Encrypt; CN=R3 +* SSL certificate verify ok. +... +``` + +That concludes this tutorial. +You have learned how to deploy cert-manager on Azure AKS and how to configure it to issue Let's Encrypt signed certificates using the DNS-01 protocol with Azure DNS. +You have learned about workload identity federation in Azure and learned how to configure cert-manager to authenticate to Azure using a Kubernetes ServiceAccount Token. + +## Cleanup + +After completing the tutorial you can clean up by deleting the cluster, the domain name and the managed identity, as follows: + +``` +az aks delete --name $CLUSTER +az network dns zone delete --name $DOMAIN_NAME +az identity delete --name $USER_ASSIGNED_IDENTITY_NAME +``` + +## Next Steps + +> 📖 Read other [cert-manager tutorials](../README.md) and [getting started guides](../../getting-started/README.md). +> +> 📖 Read more about [configuring the cert-manager ACME issuer with Azure DNS](../../configuration/acme/dns01/azuredns.md). diff --git a/content/v1.12-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md b/content/v1.12-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md new file mode 100644 index 0000000000..80f233aee7 --- /dev/null +++ b/content/v1.12-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md @@ -0,0 +1,779 @@ +--- +title: Deploy cert-manager on Google Kubernetes Engine (GKE) and create SSL certificates for Ingress using Let's Encrypt +description: Learn how to deploy cert-manager on Google Kubernetes (GKE) Engine and then configure it to sign SSL certificates using Let's Encrypt +--- + +*Last Verified: 15 July 2022* + +In this tutorial you will learn how to deploy and configure cert-manager on Google Kubernetes Engine (GKE). +You will learn how to configure cert-manager to get a signed SSL certificate from Let's Encrypt, +using an [HTTP-01 challenge](https://letsencrypt.org/docs/challenge-types/#http-01-challenge). +Finally you will learn how the certificate can be used to serve an HTTPS website with a public domain name. + +> **Google Cloud**: A suite of cloud computing services by Google.
      +> **Kubernetes**: Runs on your servers. Automates the deployment, scaling, and management of containerized applications.
      +> **cert-manager**: Runs in Kubernetes. Obtains TLS / SSL certificates and ensures the certificates are valid and up-to-date.
      +> **Let’s Encrypt**: An Internet service. Allows you to generate free short-lived SSL certificates. + +First you will create a Kubernetes (GKE) cluster and deploy a sample web server. +You will then create a public IP address and a public domain name for your website. +You'll set up Ingress and Google Cloud load balancers so that Internet clients can connect to the web server using HTTP. +Finally you will use cert-manager to get an SSL certificate from Let's Encrypt +and configure the load balancer to use that certificate. +By the end of this tutorial you will be able to connect to your website from the Internet using an `https://` URL. + +## Prerequisites + +**💻 Google Cloud account** + +You will need a Google Cloud account. +Registration requires a credit card or bank account details. +Visit the [Get started with Google Cloud](https://cloud.google.com/docs/get-started) page and follow the instructions. + +> 💵 If you have never used Google Cloud before, you may be eligible for the +> [Google Cloud Free +> Program](https://cloud.google.com/free/docs/gcp-free-tier/#free-trial), which +> gives you a 90 day trial period that includes $300 in free Cloud Billing +> credits to explore and evaluate Google Cloud. + +**💻 Domain Name** + +You will need a domain name and the ability to create DNS records in that domain. We will be getting a $12 domain name from Google Domains. Google Domains is one of the many possible "domain name registrars". NameCheap and GoDaddy are two other well-known registrars. + +> 💵 If you prefer not purchasing a domain name, it is also possible to adapt this tutorial to use the IP address to serve your website and for the SSL certificate. + +**💻 Software** + + +You will also need to install the following software on your laptop: + +1. [gcloud](https://cloud.google.com/sdk/docs/install): A set of tools to create and manage Google Cloud resources. +2. [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl): The Kubernetes command-line tool which allows you to configure Kubernetes clusters. +3. [curl](https://everything.curl.dev/get): A command-line tool for connecting to a web server using HTTP and HTTPS. + +> ℹ️ Try running `gcloud components install kubectl` to quickly install `kubectl`. + +## 0. Configure `gcloud` with a Google Cloud project + +If you don't have a Google Cloud account, the command below will create one for you: + +```bash +gcloud init +``` + +You will need to answer "yes" to the following question: + +```text +Do you want to configure a default Compute Region and Zone? (Y/n)? Y +``` + +After running the command, you will shown the project name, default region, and default zone. + +Example output: + +```text +* Commands that require authentication will use firstname.lastname@example.com by default +* Commands will reference project `your-project` by default +* Compute Engine commands will use region `europe-west1` by default +* Compute Engine commands will use zone `europe-west1-b` by default +``` + +In this tutorial, we will refer to the name of the project that was selected while running `gcloud init` with the variable `PROJECT`. Where ever you see `$PROJECT` in a command, you need to either (1) replace the variable manually before you execute the command, +or (2) export the variable in your shell session. This applies to all environment variables that you will encounter in the commands listed in this tutorial. + +We will go with option (2), so we need to export the environment variables before continuing using the information that was printed by `gcloud init`: + +```bash +export PROJECT=your-project # Your Google Cloud project ID. +export REGION=europe-west1 # Your Google Cloud region. +``` + +## 1. Create a Kubernetes Cluster + +To get started, let's create a Kubernetes cluster in Google Cloud. You will need to pick a name for your cluster. Here, we will go with "test-cluster-1". Let us save it in an environment variable: + +```bash +export CLUSTER=test-cluster-1 +``` + +Now, create the cluster using the following command: + +```bash +gcloud container clusters create $CLUSTER --preemptible --num-nodes=1 +``` + +Set up the [Google Kubernetes Engine auth plugin for kubectl](https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke): + +```bash +gcloud components install gke-gcloud-auth-plugin +export USE_GKE_GCLOUD_AUTH_PLUGIN=True +gcloud container clusters get-credentials $CLUSTER +``` + +Now check that you can connect to the cluster: + +```bash +kubectl get nodes -o wide +``` + +> ⏲ It will take 4-5 minutes to create the cluster. +> +> 💵 To minimize your cloud bill, this command creates a 1-node cluster using a +> [preemptible virtual +> machine](https://cloud.google.com/kubernetes-engine/docs/how-to/preemptible-vms) +> which is cheaper than a normal virtual machine. + +## 2. Deploy a sample web server + +We will deploy a very simple web server which responds to HTTP requests with "hello world!". + +```bash +kubectl create deployment web --image=gcr.io/google-samples/hello-app:1.0 +``` + +We also need to create a Kubernetes Service, so that connections can be routed to the web server Pods: + +```bash +kubectl expose deployment web --port=8080 +``` + +> ℹ️ These [kubectl imperative commands](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/imperative-command/) are used for readability and brevity. +> Feel free to use YAML manifests and `kubectl apply -f` instead. +> +> ℹ️ The Service created by `kubectl expose` will be of type `ClusterIP` (the default) and this is only reachable by components within the cluster. Later we will create an Ingress which is how we make the service available to clients outside the cluster. +> +> 🔰 Read more about [Using a Service to Expose Your App](https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/). + +## 3. Create a static external IP address + +This tutorial is about creating a public facing HTTPS website with a Let's Encrypt SSL certificate using the HTTP01 challenge mechanism, +so we need a public IP address so that both Let's Encrypt and other Internet clients can connect to your website. + +It is easy to create a public IP address in Google Cloud and later we will associate it with your website domain name and with a Google Cloud load balancer, which will accept HTTP(S) connections from Internet clients and proxy the requests to the web servers running in your cluster. + +Create a global static IP address as follows: + +```bash +gcloud compute addresses create web-ip --global +``` + +You should see the new IP address listed: + +```bash +gcloud compute addresses list +``` + +> ⚠️ You MUST create a `global` IP address because that is a prerequisite of the [External HTTP(S) Load Balancer](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress-xlb) which we will be using in this tutorial. +> +> 💵 Global static IP addresses are only available in the Premium network service tier and are more expensive than ephemeral and standard public IP addresses. +> +> 🔰 Read more about [Network service tiers in Google Cloud](https://cloud.google.com/network-tiers). +> +> 🔰 Read more about [Reserving a static external IP address in Google Cloud](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address). + +Finally, we will save the IP address into an environment variable for later use. Display the IP address with the following command: + +```bash +gcloud compute addresses describe web-ip --format='value(address)' --global +``` + +Then, copy the output and save it into an environment variable: + +```bash +export IP_ADDRESS=198.51.100.1 # Replace with your IP address +``` + +## 4. Create a domain name for your website + +You will need a domain name for your website and Let's Encrypt checks your domain before it signs your SSL certificate, +so the domain name needs to be reachable from the Internet. + +We will purchase a cheap domain name using a credit card. Go to https://domains.google.com, and type something in the search box. For the example, we searched for `hello-app.com` because the example container that we will be deploying is called `hello-app`. Most importantly, we make sure to sort the domain names by price: + +![](/images/getting-started/screenshot_google-domains_get-a-new-domain.png) + +We don't pick `hello-app.com` because it costs $2,800; instead, we go with the one at the top: `heyapp.net`. It looks good! We then click the cart button. On the next screen, you will want to disable the auto-renewal, since we don't want to pay for this domain every year: + +![](/images/getting-started/screenshot_google-domains_your-cart.png) + +Now that you know your domain name, save it in an environment variable: + +```bash +export DOMAIN_NAME=heyapp.net +``` + +Next, you will need to create a new `A` record pointing at the IP address that we created above. Head back to https://domains.google.com/registrar, open your domain (here, `heyapp.net`) and click "DNS" on the left menu. You will see "Custom records". You want to add a new record of type `A` and put the IP address from the previous step into "data". You must leave "Host name" empty because we are configuring the top-level domain name: + +![](/images/getting-started/screenshot_google-domains_resource-records.png) + +> 🔰 Learn more about [What is a DNS A record? from the Cloudflare DNS tutorial](https://www.cloudflare.com/learning/dns/dns-records/dns-a-record/). + +> ℹ️ It is not strictly necessary to create a domain name for your website. You can connect to it using the IP address and later you can create an SSL certificate for the IP address instead of a domain name. If for some reason you can't create a domain name, then feel free to skip this section and adapt the instructions below to use an IP address instead. +> +> ℹ️ Every Google Cloud address has an automatically generated reverse DNS name like `51.159.120.34.bc.googleusercontent.com`, +> but the parent domain `googleusercontent.com` has a CAA record which prevents +> Let's Encrypt from signing certificates for the sub-domains. +> See [Certificate Authority Authorization (CAA)](https://letsencrypt.org/docs/caa/) in the Let's Encrypt documentation. + +## 5. Create an Ingress + +You won't be able to reach your website yet. +Your web server is running inside your Kubernetes cluster but there is no route or proxy through which Internet clients can connect to it, yet! +Now we will create a Kubernetes Ingress object and in Google Cloud this will trigger the creation of a various services which together allow Internet clients to reach your web server running inside your Kubernetes cluster. + +Initially we are going to create an HTTP (not an HTTPS) Ingress so that we can test the basic connectivity before adding the SSL layer. + +Copy the following YAML into a file called `ingress.yaml` and apply it: + +```yaml +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: web-ingress + annotations: + # This tells Google Cloud to create an External Load Balancer to realize this Ingress + kubernetes.io/ingress.class: gce + # This enables HTTP connections from Internet clients + kubernetes.io/ingress.allow-http: "true" + # This tells Google Cloud to associate the External Load Balancer with the static IP which we created earlier + kubernetes.io/ingress.global-static-ip-name: web-ip +spec: + defaultBackend: + service: + name: web + port: + number: 8080 +``` + +```bash +kubectl apply -f ingress.yaml +``` + +This will trigger the creation of a Google HTTP(S) loadbalancer associated with the IP address that you created earlier. +You can watch the progress and the resources that are being created: + +```bash +kubectl describe ingress web-ingress +``` + +Within 4-5 minutes all the load balancer components should be ready and you should be able to connect to the DNS name and see the response from the hello-world web server that we deployed earlier: + +``` +curl http://$DOMAIN_NAME +``` + +Example output: + +```console +Hello, world! +Version: 1.0.0 +Hostname: web-79d88c97d6-t8hj2 +``` + +At this point we have a Google load balancer which is forwarding HTTP traffic to the hello-world web server running in a Pod in our cluster. + +> ⏲ It may take 4-5 minutes for the load balancer components to be created and +> configured and for Internet clients to be routed to your web server. +> Refer to the [Troubleshooting](#troubleshooting) section if it takes longer. +> +> 🔰 Read about how to [Use a static IP addresses for HTTP(S) load balancers via Ingress annotation](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress-xlb#static_ip_addresses_for_https_load_balancers). +> +> 🔰 Read a [Summary of external Ingress annotations for GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/load-balance-ingress#summary_of_external_ingress_annotations). +> +> 🔰 Read about [Troubleshooting Ingress with External HTTP(S) Load Balancing on GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/load-balance-ingress#testing_the). +> +> ℹ️ There are two Ingress classes available for GKE Ingress. The `gce` class deploys an external load balancer and the `gce-internal` class deploys an internal load balancer. Ingress resources without a class specified default to `gce`. +> +> ⚠️ Contrary to the Kubernetes Ingress documentation, you MUST use the `kubernetes.io/ingress.class` annotation rather than the `Ingress.Spec.IngressClassName` field. +> See [ingress-gce #1301](https://github.com/kubernetes/ingress-gce/issues/1301#issuecomment-1133356812) and [ingress-gce #1337](https://github.com/kubernetes/ingress-gce/pull/1337). + + +## 6. Install cert-manager + +So finally we are ready to start creating an SSL certificate for our website. +The first thing you need to do is install cert-manager, and we'll install it the easy using `kubectl` as follows: + +``` +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.2/cert-manager.yaml +``` + +This will create three Deployments, and a bunch of Services and Pods in a new namespace called `cert-manager`. +It also installs various cluster scoped supporting resources such as RBAC roles and Custom Resource Definitions. + +You can view some of the resources that have been installed as follows: + +```bash +kubectl -n cert-manager get all +``` + +And you can explore the Custom Resource Definitions (cert-manager's API) using `kubectl explain`, as follows: + +```bash +kubectl explain Certificate +kubectl explain CertificateRequest +kubectl explain Issuer +``` + +> 🔰 Read about [other ways to install cert-manager](../../installation). +> +> 🔰 Read more about [Certificates and Issuers](../../concepts). + +## 7. Create an Issuer for Let's Encrypt Staging + +An Issuer is a custom resource which tells cert-manager how to sign a Certificate. +In this case the Issuer will be configured to connect to the Let's Encrypt staging server, +which allows us to test everything without using up our Let's Encrypt certificate quota for the domain name. + +> ℹ️ Let's Encrypt uses the Automatic Certificate Management Environment (ACME) protocol +> which is why the configuration below is under a key called `acme`. + +Save the following content to a file called `issuer-lets-encrypt-staging.yaml`, change the `email` field to use your email address and apply it: + +```yaml +# issuer-lets-encrypt-staging.yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: # ❗ Replace this with your email address + privateKeySecretRef: + name: letsencrypt-staging + solvers: + - http01: + ingress: + name: web-ingress +``` + +```bash +kubectl apply -f issuer-lets-encrypt-staging.yaml +``` + +> ℹ️ The email address is only used by Let's Encrypt to remind you to renew the certificate after 30 days before expiry. You will only receive this email if something goes wrong when renewing the certificate with cert-manager. + +You can check the status of the Issuer: + +```bash +kubectl describe issuers.cert-manager.io letsencrypt-staging +``` + +Example output + +```console +Status: + Acme: + Last Registered Email: firstname.lastname@example.com + Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/60706744 + Conditions: + Last Transition Time: 2022-07-13T16:13:25Z + Message: The ACME account was registered with the ACME server + Observed Generation: 1 + Reason: ACMEAccountRegistered + Status: True + Type: Ready +``` + +> ℹ️ The Let's Encrypt production issuer has [very strict rate limits](https://letsencrypt.org/docs/rate-limits/). +> When you're experimenting and learning, it can be very easy to hit those limits. Because of that risk, +> we'll start with the Let's Encrypt staging issuer, and once we're happy that it's working +> we'll switch to the production issuer. +> +> ⚠️ In the next step you will see a warning about untrusted certificates because +> we start with the staging issuer, but that's totally expected. +> +> 🔰 Read more about [configuring the ACME Issuer](../../configuration/acme). + +## 8. Re-configure the Ingress for SSL + +Earlier we created an Ingress and saw that we could connect to our web server using HTTP. +Now we will reconfigure that Ingress for HTTPS. + +First a quick hack, to work around a problem with the Google Cloud ingress controller. +Create an empty Secret for your SSL certificate **before reconfiguring the Ingress** and apply it: + +```yaml +# secret.yaml +apiVersion: v1 +kind: Secret +metadata: + name: web-ssl +type: kubernetes.io/tls +stringData: + tls.key: "" + tls.crt: "" +``` + +```bash +kubectl apply -f secret.yaml +``` + +> ℹ️ This is a work around for a chicken-and-egg problem, where the ingress-gce +> controller won't update its forwarding rules unless it can first find the +> Secret that will eventually contain the SSL certificate. But Let's Encrypt +> won't sign the SSL certificate until it can get the special +> `.../.well-known/acme-challenge/...` URL which cert-manager adds to the +> Ingress and which must then be translated into Google Cloud forwarding rules, +> by the ingress-gce controller. +> +> 🔰 Read more about [Kubernetes Secrets and how to use them](https://kubernetes.io/docs/concepts/configuration/secret/). + +Now make the following changes to the Ingress and apply them: + +```diff +--- a/ingress.yaml ++++ b/ingress.yaml +@@ -7,7 +7,12 @@ metadata: + kubernetes.io/ingress.class: gce + kubernetes.io/ingress.allow-http: "true" + kubernetes.io/ingress.global-static-ip-name: web-ip ++ cert-manager.io/issuer: letsencrypt-staging + spec: ++ tls: ++ - secretName: web-ssl ++ hosts: ++ - $DOMAIN_NAME + defaultBackend: + service: + name: web +``` + +``` +kubectl apply -f ingress.yaml +``` + +This triggers a complex set of operations which may take many minutes to eventually complete. +Some of these steps take 2-3 minutes and some will initially fail. +They should all eventually succeed because cert-manager and ingress-gce (the Google Cloud ingress controller) will periodically re-reconcile. + +Eventually, When all the pieces are in place, you should be able to +use curl to check the HTTPS connection to your website: + +```bash +curl -v --insecure https://$DOMAIN_NAME +``` + +You should see that the HTTPS connection is established but that the SSL certificate is not trusted; +that's why you use the `--insecure` flag at this stage + +Example output: +```console +* Server certificate: +* subject: CN=www.example.com +* start date: Jul 14 08:52:29 2022 GMT +* expire date: Oct 12 08:52:28 2022 GMT +* issuer: C=US; O=(STAGING) Let's Encrypt; CN=(STAGING) Artificial Apricot R3 +* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. +``` + +> ⏲ You will have to wait 5-10 minutes for the SSL certificate to be signed and then loaded by the Google Cloud load balancer. +> Refer to the [Troubleshooting](#troubleshooting) section if it takes longer. +> +> ℹ️ Adding the annotation `cert-manager.io/issuer: letsencrypt-staging` marks the Ingress for the attention of the cert-manager `ingress-shim` +> and causes it to create a new Certificate with a reference to the Issuer that we created earlier. +> +> 🔰 Read [Securing Ingress Resources](../../usage/ingress.md) to learn more. +> +> 🔰 Read about how to [Specify certificates for your Ingress in GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-multi-ssl#specifying_certificates_for_your_ingress). + +## 9. Create a production ready SSL certificate + +Now that everything is working with the Let's Encrypt staging server, we can switch to the production server and get a trusted SSL certificate. + +Create a Let's Encrypt production Issuer and apply it: + +```yaml +# issuer-lets-encrypt-production.yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-production +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: # ❗ Replace this with your email address + privateKeySecretRef: + name: letsencrypt-production + solvers: + - http01: + ingress: + name: web-ingress +``` + +```bash +kubectl apply -f issuer-lets-encrypt-production.yaml +``` + +Then update the Ingress annotation to use the production Issuer: + +```bash +kubectl annotate ingress web-ingress cert-manager.io/issuer=letsencrypt-production --overwrite +``` + +This will trigger cert-manager to get a new SSL certificate signed by the Let's Encrypt production CA and store it to the `web-ssl` Secret. +Within about 10 minutes, this new certificate will be synced to the Google Cloud load balancer and you will be able to connect to the website using secure HTTPS: + +```bash +curl -v https://$DOMAIN_NAME +``` + +Example output: +```console +... +* Server certificate: +* subject: CN=www.example.com +* start date: Jul 14 09:44:29 2022 GMT +* expire date: Oct 12 09:44:28 2022 GMT +* subjectAltName: host "www.example.com" matched cert's "www.example.com" +* issuer: C=US; O=Let's Encrypt; CN=R3 +* SSL certificate verify ok. +... +Hello, world! +Version: 1.0.0 +Hostname: web-79d88c97d6-t8hj2 +``` + +It should also be possible to visit `https://$DOMAIN_NAME` in your web browser, without any errors or warnings. + +That concludes the tutorial. +You now understand how cert-manager integrates with Kubernetes Ingress and cloud Ingress controllers. +You have learned how to use cert-manager to get free Let's Encrypt SSL certificates. +And you have seen how the certificates can be used by a cloud based load balancer to terminate SSL connections from Internet clients +and forward HTTPS requests to a web server running in your Kubernetes cluster. + +> 💵 Read the [Clean up](#clean-up) section to learn how to delete all the resources that you created in this tutorial and reduce your cloud bill. +> +> 🔰 Read the [Troubleshooting](#troubleshooting) section if you encounter difficulties with the steps described in this tutorial. + +## Clean up + +After completing the tutorial you can clean up by deleting the cluster and the domain name and the static IP as follows: + +```bash +# Delete the cluster and all the Google Cloud resources related to the Ingress that it contains +gcloud container clusters delete $CLUSTER + +# Delete the domain name +gcloud dns record-sets delete $DOMAIN_NAME --zone $ZONE --type A + +# Delete the static IP address +gcloud compute addresses delete web-ip --global +``` + +## Troubleshooting + +When you create or update the Ingress object in this tutorial it triggers a complex set of operations which may take many minutes to eventually complete. +Some of these steps take 2-3 minutes and some will initially fail but then subsequently succeed when either cert-manager or the Google ingress controller re-reconciles. +In short, you should allow 5-10 minutes after you create or change the Ingress and you should expect to see some errors and warnings when you run `kubectl describe ingress web-ingress`. + +Here's a brief summary of the operations performed by cert-manager and ingress-gce (the Google Cloud Ingress controller): + +* cert-manager connects to Let's Encrypt and sends an SSL certificate signing request. +* Let's Encrypt responds with a "challenge", which is a unique token that cert-manager must make available at a well-known location on the target web site. This proves that you are an administrator of that web site and domain name. +* cert-manager deploys a Pod containing a temporary web server that serves the Let's Encrypt challenge token. +* cert-manager reconfigures the Ingress, adding a `rule` to route requests for from Let's Encrypt to that temporary web server. +* Google Cloud ingress controller reconfigures the external HTTP load balancer with that new rule. +* Let's Encrypt now connects and receives the expected challenge token and the signs the SSL certificate and returns it to cert-manager. +* cert-manager stores the signed SSL certificate in the Kubernetes Secret called `web-ssl`. +* Google Cloud ingress controller uploads the signed certificate and associated private key to a Google Cloud certificate. +* Google Cloud ingress controller reconfigures the external load balancer to serve the uploaded SSL certificate. + +### Check Ingress and associated events + +Use `kubectl describe` to view the Ingress configuration and all the associated Events. +Check that the IP address is correct and that the TLS and Host entries match the domain name that you chose for your website. +Notice that `ingress-gce` creates an Event for each of the Google Cloud components that it manages. +And notice that it adds annotations with references to the ID of each of those components. +cert-manager also creates Events when it reconciles the Ingress object, including details of the Certificate object that it creates for the Ingress. + +```console +$ kubectl describe ingress web-ingress +Name: web-ingress +Labels: +Namespace: default +Address: 34.120.159.51 +Ingress Class: +Default backend: web:8080 (10.52.0.13:8080) +TLS: + web-ssl terminates www.example.com +Rules: + Host Path Backends + ---- ---- -------- + * * web:8080 (10.52.0.13:8080) +Annotations: cert-manager.io/issuer: letsencrypt-staging + ingress.kubernetes.io/backends: {"k8s1-01784147-default-web-8080-1647ccd2":"HEALTHY"} + ingress.kubernetes.io/forwarding-rule: k8s2-fr-1lt9dzcy-default-web-ingress-yteotwe4 + ingress.kubernetes.io/https-forwarding-rule: k8s2-fs-1lt9dzcy-default-web-ingress-yteotwe4 + ingress.kubernetes.io/https-target-proxy: k8s2-ts-1lt9dzcy-default-web-ingress-yteotwe4 + ingress.kubernetes.io/ssl-cert: k8s2-cr-1lt9dzcy-4gjeakdb9n7k6ls7-a257650b5fefd174 + ingress.kubernetes.io/target-proxy: k8s2-tp-1lt9dzcy-default-web-ingress-yteotwe4 + ingress.kubernetes.io/url-map: k8s2-um-1lt9dzcy-default-web-ingress-yteotwe4 + kubernetes.io/ingress.allow-http: true + kubernetes.io/ingress.class: gce + kubernetes.io/ingress.global-static-ip-name: web-ip +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CreateCertificate 28m cert-manager-ingress-shim Successfully created Certificate "web-ssl" + Normal Sync 28m loadbalancer-controller UrlMap "k8s2-um-1lt9dzcy-default-web-ingress-yteotwe4" updated + Warning Sync 24m (x16 over 28m) loadbalancer-controller Error syncing to GCP: error running load balancer syncing routine: loadbalancer 1lt9dzcy-default-web-ingress-yteotwe4 does not exist: googleapi: Error 404: The resource 'projects/your-project/global/sslCertificates/k8s2-cr-1lt9dzcy-4gjeakdb9n7k6ls7-e3b0c44298fc1c14' was not found, notFound + Normal Sync 34s (x16 over 65m) loadbalancer-controller Scheduled for sync +``` + +### Use cmctl to show the state of a Certificate and its associated resources + +> ℹ️ [Install `cmctl`](../../reference/cmctl.md) if you have not already done so. + +When you create a Certificate, cert-manager will create a collection of temporary resources +which each contain information about the status of certificate signing process. +You can read more about these in the [Certificate Lifecycle](../../concepts/certificate.md#certificate-lifecycle) section. +Use the `cmctl status` command to view details of all these resources and all the associated Events and error messages. + +You may see some temporary errors, like: + +```console +$ cmctl status certificate web-ssl +Name: web-ssl +Namespace: default +Created at: 2022-07-14T17:30:06+01:00 +Conditions: + Ready: False, Reason: MissingData, Message: Issuing certificate as Secret does not contain a private key + Issuing: True, Reason: MissingData, Message: Issuing certificate as Secret does not contain a private key +DNS Names: +- www.example.com +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Issuing 4m37s cert-manager-certificates-trigger Issuing certificate as Secret does not contain a private key + Normal Generated 4m37s cert-manager-certificates-key-manager Stored new private key in temporary Secret resource "web-ssl-8gsqc" + Normal Requested 4m37s cert-manager-certificates-request-manager Created new CertificateRequest resource "web-ssl-dblrj" +Issuer: + Name: letsencrypt-staging + Kind: Issuer + Conditions: + Ready: True, Reason: ACMEAccountRegistered, Message: The ACME account was registered with the ACME server + Events: +error: 'tls.crt' of Secret "web-ssl" is not set +Not Before: +Not After: +Renewal Time: +CertificateRequest: + Name: web-ssl-dblrj + Namespace: default + Conditions: + Approved: True, Reason: cert-manager.io, Message: Certificate request has been approved by cert-manager.io + Ready: False, Reason: Pending, Message: Waiting on certificate issuance from order default/web-ssl-dblrj-327645514: "pending" + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal cert-manager.io 4m37s cert-manager-certificaterequests-approver Certificate request has been approved by cert-manager.io + Normal OrderCreated 4m37s cert-manager-certificaterequests-issuer-acme Created Order resource default/web-ssl-dblrj-327645514 + Normal OrderPending 4m37s cert-manager-certificaterequests-issuer-acme Waiting on certificate issuance from order default/web-ssl-dblrj-327645514: "" +Order: + Name: web-ssl-dblrj-327645514 + State: pending, Reason: + Authorizations: + URL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/3008789144, Identifier: www.example.com, Initial State: pending, Wildcard: false +Challenges: +- Name: web-ssl-dblrj-327645514-2671694319, Type: HTTP-01, Token: TKspp86xMjQzTvMVXWkezEA2sE2GSWjnld5Lt4X13ro, Key: TKspp86xMjQzTvMVXWkezEA2sE2GSWjnld5Lt4X13ro.f4bppCOm-jXasFGMKjpBE5aQlhiQBeTPIs0Lx822xao, State: pending, Reason: Waiting for HTTP-01 challenge propagation: did not get expected response when querying endpoint, expected "TKspp86xMjQzTvMVXWkezEA2sE2GSWjnld5Lt4X13ro.f4bppCOm-jXasFGMKjpBE5aQlhiQBeTPIs0Lx822xao" but got: Hello, world! +Version: 1... (truncated), Processing: true, Presented: true +``` + +This is because cert-manager is performing a preflight check to see if the temporary challenge web server is reachable at the expected URL. +Initially it will not be reachable, because cert-manager takes some time to deploy the temporary web server and the Ingress controller takes time to set up the new HTTP routing rules. +Eventually you will see that the Certificate is Ready and signed: + +```console +$ cmctl status certificate web-ssl +Name: web-ssl +Namespace: default +Created at: 2022-07-14T17:30:06+01:00 +Conditions: + Ready: True, Reason: Ready, Message: Certificate is up to date and has not expired +DNS Names: +- www.example.com +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Issuing 31m cert-manager-certificates-trigger Issuing certificate as Secret does not contain a private key + Normal Generated 31m cert-manager-certificates-key-manager Stored new private key in temporary Secret resource "web-ssl-8gsqc" + Normal Requested 31m cert-manager-certificates-request-manager Created new CertificateRequest resource "web-ssl-dblrj" + Normal Issuing 26m cert-manager-certificates-issuing The certificate has been successfully issued +Issuer: + Name: letsencrypt-staging + Kind: Issuer + Conditions: + Ready: True, Reason: ACMEAccountRegistered, Message: The ACME account was registered with the ACME server + Events: +Secret: + Name: web-ssl + Issuer Country: US + Issuer Organisation: (STAGING) Let's Encrypt + Issuer Common Name: (STAGING) Artificial Apricot R3 + Key Usage: Digital Signature, Key Encipherment + Extended Key Usages: Server Authentication, Client Authentication + Public Key Algorithm: RSA + Signature Algorithm: SHA256-RSA + Subject Key ID: a51e3621f5c1138947810f27dce425b33c88cb16 + Authority Key ID: de727a48df31c3a650df9f8523df57374b5d2e65 + Serial Number: fa8bb0b603ca2cdbfdfb2872d05ee52cda10 + Events: +Not Before: 2022-07-14T16:34:52+01:00 +Not After: 2022-10-12T16:34:51+01:00 +Renewal Time: 2022-09-12T16:34:51+01:00 +``` + +### Check that the SSL certificate has been copied to Google Cloud + +After cert-manager receives the signed Certificate it stores in the `web-ssl` Secret, +and this in turn triggers the Google Cloud ingress controller to copy that SSL certificate to Google Cloud. +You can see the certificate using the `gcloud` command, as follows: + +```console +$ gcloud compute ssl-certificates list +NAME TYPE CREATION_TIMESTAMP EXPIRE_TIME MANAGED_STATUS +k8s2-cr-1lt9dzcy-4gjeakdb9n7k6ls7-a257650b5fefd174 SELF_MANAGED 2022-07-14T09:37:06.920-07:00 2022-10-12T08:34:51.000-07:00 +``` + +And you can view its contents and check its attributes as follows: + +```console +$ gcloud compute ssl-certificates describe k8s2-cr-1lt9dzcy-4gjeakdb9n7k6ls7-a257650b5fefd174 --format='value(certificate)' \ + | openssl x509 -in - -noout -text +... +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 04:9f:47:f1:cb:25:37:9b:86:a3:ef:bf:2e:77:3b:45:fc:1a + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, O = Let's Encrypt, CN = R3 + Validity + Not Before: Jul 14 17:11:15 2022 GMT + Not After : Oct 12 17:11:14 2022 GMT + Subject: CN = www.example.com +``` + +### Check the Google Cloud forwarding-rules + +After you add the TLS stanza to the Ingress object, you should eventually see a forwarding-rule for the SSL connection: + +```console +$ gcloud compute forwarding-rules describe k8s2-fs-1lt9dzcy-default-web-ingress-yteotwe4 --global +IPAddress: 34.120.159.51 +IPProtocol: TCP +creationTimestamp: '2022-07-14T09:37:12.362-07:00' +description: '{"kubernetes.io/ingress-name": "default/web-ingress"}' +fingerprint: oBTg7dRaIqI= +id: '2303318464959215831' +kind: compute#forwardingRule +labelFingerprint: 42WmSpB8rSM= +loadBalancingScheme: EXTERNAL +name: k8s2-fs-1lt9dzcy-default-web-ingress-yteotwe4 +networkTier: PREMIUM +portRange: 443-443 +selfLink: https://www.googleapis.com/compute/v1/projects/your-project/global/forwardingRules/k8s2-fs-1lt9dzcy-default-web-ingress-yteotwe4 +target: https://www.googleapis.com/compute/v1/projects/your-project/global/targetHttpsProxies/k8s2-ts-1lt9dzcy-default-web-ingress-yteotwe4 +``` diff --git a/content/v1.12-docs/tutorials/istio-csr/example/example-cluster-issuer.yaml b/content/v1.12-docs/tutorials/istio-csr/example/example-cluster-issuer.yaml new file mode 100644 index 0000000000..cb387c29d7 --- /dev/null +++ b/content/v1.12-docs/tutorials/istio-csr/example/example-cluster-issuer.yaml @@ -0,0 +1,46 @@ +# NB: We generally recommend using Issuers rather than ClusterIssuers with istio-csr. +# Issuers are easier to scope, and therefore easier to reason about in terms of security. + +# SelfSigned issuers are useful for creating root certificates +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned +spec: + selfSigned: {} +--- +# Request a self-signed certificate from our ClusterIssuer; this will function as our +# issuing root certificate when we pass it into a CA ClusterIssuer. + +# It's generally fine to issue root certificates like this one with long lifespans; +# the certificates which istio-csr issues will be much shorter lived. +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: istio-ca + namespace: cert-manager +spec: + isCA: true + duration: 87600h # 10 years + secretName: istio-ca + commonName: istio-ca + privateKey: + algorithm: ECDSA + size: 256 + subject: + organizations: + - cluster.local + - cert-manager + issuerRef: + name: selfsigned + kind: ClusterIssuer + group: cert-manager.io +--- +# Create a CA issuer using our root. This will be the ClusterIssuer which istio-csr will use. +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: istio-ca +spec: + ca: + secretName: istio-ca diff --git a/content/v1.12-docs/tutorials/istio-csr/example/example-issuer.yaml b/content/v1.12-docs/tutorials/istio-csr/example/example-issuer.yaml new file mode 100644 index 0000000000..4dd383dbcb --- /dev/null +++ b/content/v1.12-docs/tutorials/istio-csr/example/example-issuer.yaml @@ -0,0 +1,45 @@ +# SelfSigned issuers are useful for creating root certificates +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned + namespace: istio-system +spec: + selfSigned: {} +--- +# Request a self-signed certificate from our Issuer; this will function as our +# issuing root certificate when we pass it into a CA Issuer. + +# It's generally fine to issue root certificates like this one with long lifespans; +# the certificates which istio-csr issues will be much shorter lived. +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: istio-ca + namespace: istio-system +spec: + isCA: true + duration: 87600h # 10 years + secretName: istio-ca + commonName: istio-ca + privateKey: + algorithm: ECDSA + size: 256 + subject: + organizations: + - cluster.local + - cert-manager + issuerRef: + name: selfsigned + kind: Issuer + group: cert-manager.io +--- +# Create a CA issuer using our root. This will be the Issuer which istio-csr will use. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: istio-ca + namespace: istio-system +spec: + ca: + secretName: istio-ca diff --git a/content/v1.12-docs/tutorials/istio-csr/example/istio-config-getting-started.yaml b/content/v1.12-docs/tutorials/istio-csr/example/istio-config-getting-started.yaml new file mode 100644 index 0000000000..b0b6e1ca4c --- /dev/null +++ b/content/v1.12-docs/tutorials/istio-csr/example/istio-config-getting-started.yaml @@ -0,0 +1,57 @@ +apiVersion: install.istio.io/v1alpha1 +kind: IstioOperator +metadata: + namespace: istio-system +spec: + profile: "demo" + hub: gcr.io/istio-release + meshConfig: + # Change the following line to configure the trust domain of the Istio cluster. + trustDomain: cluster.local + values: + global: + # Change certificate provider to cert-manager istio agent for istio agent + caAddress: cert-manager-istio-csr.cert-manager.svc:443 + components: + pilot: + k8s: + env: + # Disable istiod CA Sever functionality + - name: ENABLE_CA_SERVER + value: "false" + overlays: + - apiVersion: apps/v1 + kind: Deployment + name: istiod + patches: + + # Mount istiod serving and webhook certificate from Secret mount + - path: spec.template.spec.containers.[name:discovery].args[-1] + value: "--tlsCertFile=/etc/cert-manager/tls/tls.crt" + - path: spec.template.spec.containers.[name:discovery].args[-1] + value: "--tlsKeyFile=/etc/cert-manager/tls/tls.key" + - path: spec.template.spec.containers.[name:discovery].args[-1] + value: "--caCertFile=/etc/cert-manager/ca/root-cert.pem" + + - path: spec.template.spec.containers.[name:discovery].volumeMounts[-1] + value: + name: cert-manager + mountPath: "/etc/cert-manager/tls" + readOnly: true + - path: spec.template.spec.containers.[name:discovery].volumeMounts[-1] + value: + name: ca-root-cert + mountPath: "/etc/cert-manager/ca" + readOnly: true + + - path: spec.template.spec.volumes[-1] + value: + name: cert-manager + secret: + secretName: istiod-tls + - path: spec.template.spec.volumes[-1] + value: + name: ca-root-cert + configMap: + defaultMode: 420 + name: istio-ca-root-cert diff --git a/content/v1.12-docs/tutorials/istio-csr/istio-csr.md b/content/v1.12-docs/tutorials/istio-csr/istio-csr.md new file mode 100644 index 0000000000..32c5c1ae3e --- /dev/null +++ b/content/v1.12-docs/tutorials/istio-csr/istio-csr.md @@ -0,0 +1,276 @@ +--- +title: Securing the istio Service Mesh using cert-manager +description: 'cert-manager tutorials: Securing the istio Service Mesh using cert-manager' +--- + +This guide will run through installing and using istio-csr from scratch. We'll use [kind](https://kind.sigs.k8s.io/) to create a new cluster locally in Docker, but this guide should work on any cluster as long as the relevant Istio [Platform Setup](https://istio.io/latest/docs/setup/platform-setup/) has been performed. + +Note that if you're following the Platform Setup guide for OpenShift, do not run the `istioctl install` command listed in that guide; we'll run our own command later. + +## Initial Setup + +You'll need the following tools installed on your machine: + +- [istioctl](https://github.com/istio/istio/releases/latest) +- [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) and [docker](https://docs.docker.com/get-docker/) (if you're using kind) +- [helm](https://helm.sh/docs/intro/install/) +- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) +- [jq](https://stedolan.github.io/jq/download/) + +In addition, Istio must not already be installed in your cluster. Installing istio-csr _after_ Istio is not supported. + +## Creating the Cluster and Installing cert-manager + +Kind will automatically set up kubectl to point to the newly created cluster. + +We install cert-manager [using helm](https://cert-manager.io/docs/installation/helm/) here, but if you've got a preferred method you can install in any way. + +```console +kind create cluster --image=docker.io/kindest/node:v1.22.4 + +# Helm setup +helm repo add jetstack https://charts.jetstack.io +helm repo update + +# install cert-manager CRDs +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.0/cert-manager.crds.yaml + +# install cert-manager; this might take a little time +helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.8.0 + +# We need this namespace to exist since our cert will be placed there +kubectl create namespace istio-system +``` + +## Create a cert-manager Issuer and Issuing Certificate + +An Issuer tells cert-manager how to issue certificates; we'll create a self-signed root CA in our cluster because it's really simple to configure. + +The approach of using a locally generated root certificate would work in a production deployment too, but there are also several [other issuers](https://cert-manager.io/docs/configuration/) in cert-manager which could be used. Note that the ACME issuer **will not work**, since it can't add the required fields to issued certificates. + +There are also some comments on the [example-issuer](https://github.com/cert-manager/website/blob/master/content/docs/tutorials/istio-csr/example/example-issuer.yaml) providing a little more detail. Note also that this guide only uses `Issuer`s and not `ClusterIssuer`s - using a `ClusterIssuer` isn't a drop-in replacement, and in any case we recommend that production deployments use Issuers for easier access controls and scoping. + +```console +kubectl apply -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/istio-csr/example/example-issuer.yaml +``` + +## Export the Root CA to a Local File + +While it's possible to configure Istio such that it can automatically "discover" the root CA, this can be dangerous in +some specific scenarios involving other security holes, enabling [signer hijacking attacks](https://github.com/cert-manager/istio-csr/issues/103#issuecomment-923882792). + +As such, we'll export our Root CA and configure Istio later using that static cert. + +```console +# Export our cert from the secret it's stored in, and base64 decode to get the PEM data. +kubectl get -n istio-system secret istio-ca -ogo-template='{{index .data "tls.crt"}}' | base64 -d > ca.pem + +# Out of interest, we can check out what our CA looks like +openssl x509 -in ca.pem -noout -text + +# Add our CA to a secret +kubectl create secret generic -n cert-manager istio-root-ca --from-file=ca.pem=ca.pem +``` + +## Installing istio-csr + +istio-csr is best installed via Helm, and it should be simple and quick to install. There +are a bunch of other configuration options for the helm chart, which you can check out [here](../../projects/istio-csr.md). + +```console +helm repo add jetstack https://charts.jetstack.io +helm repo update + +# We set a few helm template values so we can point at our static root CA +helm install -n cert-manager cert-manager-istio-csr jetstack/cert-manager-istio-csr \ + --set "app.tls.rootCAFile=/var/run/secrets/istio-csr/ca.pem" \ + --set "volumeMounts[0].name=root-ca" \ + --set "volumeMounts[0].mountPath=/var/run/secrets/istio-csr" \ + --set "volumes[0].name=root-ca" \ + --set "volumes[0].secret.secretName=istio-root-ca" + +# Check to see that the istio-csr pod is running and ready +kubectl get pods -n cert-manager +NAME READY STATUS RESTARTS AGE +cert-manager-aaaaaaaaaa-11111 1/1 Running 0 9m46s +cert-manager-cainjector-aaaaaaaaaa-22222 1/1 Running 0 9m46s +cert-manager-istio-csr-bbbbbbbbbb-00000 1/1 Running 0 63s +cert-manager-webhook-aaaaaaaaa-33333 1/1 Running 0 9m46s +``` + +## Installing Istio + +If you're not running on kind, you may need to do some additional [setup tasks](https://istio.io/latest/docs/setup/platform-setup/) before installing Istio. + +We use the `istioctl` CLI to install Istio, configured using a custom IstioOperator manifest. + +The custom manifest does the following: + +- Disables the CA server in istiod, +- Ensures that Istio workloads request certificates from istio-csr, +- Ensures that the istiod certificates and keys are mounted from the Certificate created when installing istio-csr. + +First we download our demo manifest and then we apply it. + +```console +curl -sSL https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/istio-csr/example/istio-config-getting-started.yaml > istio-install-config.yaml +``` + +You may wish to inspect and tweak `istio-install-config.yaml` if you know what you're doing, +but this manifest should work for example purposes as-is. + +If you set a custom `app.tls.trustDomain` when installing istio-csr via helm earlier, you'll need to ensure that +value is repeated in `istio-install-config.yaml`. + +This final command will install Istio; the exact command you need might vary on different platforms, +and will certainly vary on OpenShift. + +```console +# This takes a little time to complete +istioctl install -f istio-install-config.yaml + +# If you're on OpenShift, you need a different profile: +# istioctl install --set profile=openshift -f istio-install-config.yaml +``` + +You will be prompted for input to confirm your choice of Istio profile: + +```console +This will install the Istio 1.14.1 demo profile with ["Istio core" "Istiod" "Ingress gateways" "Egress gateways"] components into the cluster. Proceed? (y/N) +``` + +Confirm your selection by entering `y` into the console to proceed with installation. + +## Validating Install + +The following steps are option but can be followed to validate everything is hooked correctly: + +1. Deploy a sample application & watch for `certificaterequests.cert-manager.io` resources +2. Verify `cert-manager` logs for new `certificaterequests` and responses +3. Verify the CA Endpoint being used in a `istio-proxy` sidecar container +4. Using `istioctl` to fetch the certificate info for the `istio-proxy` container + +To see this all in action, lets deploy a very simple sample application from the +[Istio samples](https://github.com/istio/istio/tree/master/samples/httpbin). + +First set some environment variables whose values could be changed if needed: + +```shell +# Set namespace for sample application +export NAMESPACE=default +# Set env var for the value of the app label in manifests +export APP=httpbin +# Grab the installed version of istio +export ISTIO_VERSION=$(istioctl version -o json | jq -r '.meshVersion[0].Info.version') +``` + +We use the `default` namespace for simplicity, so let's label the namespace for Istio injection: + +```shell +kubectl label namespace $NAMESPACE istio-injection=enabled --overwrite +``` + +In a separate terminal you should now follow the logs for `cert-manager`: + +```shell +kubectl logs -n cert-manager $(kubectl get pods -n cert-manager -o jsonpath='{.items..metadata.name}' --selector app=cert-manager) --since 2m -f +``` + +In another separate terminal, lets watch the `istio-system` namespace for `certificaterequests`: + +```shell +kubectl get certificaterequests.cert-manager.io -n istio-system -w +``` + +Now deploy the sample application `httpbin` in the labeled namespace. Note the use of a +variable to match the manifest version to your installed Istio version: + +```shell +kubectl apply -n $NAMESPACE -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/httpbin/httpbin.yaml +``` + +You should see something similar to the output here for `certificaterequests`: + +``` +NAME APPROVED DENIED READY ISSUER REQUESTOR AGE +istio-ca-74bnl True True selfsigned system:serviceaccount:cert-manager:cert-manager 2d2h +istiod-w9zh6 True True istio-ca system:serviceaccount:cert-manager:cert-manager 27m +istio-csr-8ddcs istio-ca system:serviceaccount:cert-manager:cert-manager-istio-csr 0s +istio-csr-8ddcs True istio-ca system:serviceaccount:cert-manager:cert-manager-istio-csr 0s +istio-csr-8ddcs True True istio-ca system:serviceaccount:cert-manager:cert-manager-istio-csr 0s +istio-csr-8ddcs True True istio-ca system:serviceaccount:cert-manager:cert-manager-istio-csr 0s +``` + +The key request being `istio-csr-8ddcs` in our example output. You should then check your +`cert-manager` log output for two log lines with this request being "Approved" and "Ready": + +``` +I0113 16:51:59.186482 1 conditions.go:261] Setting lastTransitionTime for CertificateRequest "istio-csr-8ddcs" condition "Approved" to 2022-01-13 16:51:59.186455713 +0000 UTC m=+3507.098466775 +I0113 16:51:59.258876 1 conditions.go:261] Setting lastTransitionTime for CertificateRequest "istio-csr-8ddcs" condition "Ready" to 2022-01-13 16:51:59.258837897 +0000 UTC m=+3507.170859959 +``` + +You should now see the application is running with both the application container and the sidecar: + +```shell +~ kubectl get pods -n $NAMESPACE +NAME READY STATUS RESTARTS AGE +httpbin-74fb669cc6-559cg 2/2 Running 0 4m +``` + +To validate that the `istio-proxy` sidecar container has requested the certificate from the correct +service, check the container logs: + +```shell +kubectl logs $(kubectl get pod -n $NAMESPACE -o jsonpath="{.items...metadata.name}" --selector app=$APP) -c istio-proxy +``` + +You should see some early logs similar to this example: + +Istio v1.12 and earlier versions: + +``` +2022-01-13T16:51:58.495493Z info CA Endpoint cert-manager-istio-csr.cert-manager.svc:443, provider Citadel +2022-01-13T16:51:58.495817Z info Using CA cert-manager-istio-csr.cert-manager.svc:443 cert with certs: var/run/secrets/istio/root-cert.pem +2022-01-13T16:51:58.495941Z info citadelclient Citadel client using custom root cert: cert-manager-istio-csr.cert-manager.svc:443 +``` + +Istio v1.13+ + +``` +2022-01-13T16:51:58.495493Z info CA Endpoint cert-manager-istio-csr.cert-manager.svc:443, provider Citadel +2022-01-13T16:51:58.495817Z info Using CA cert-manager-istio-csr.cert-manager.svc:443 cert with certs: var/run/secrets/istio/root-cert.pem +2022-01-13T16:51:58.495941Z info citadelclient Citadel client using custom root cert: var/run/secrets/istio/root-cert.pem +``` + +Finally we can inspect the certificate being used in memory by Envoy. This one liner should return you the certificate being used: + +```shell +istioctl proxy-config secret $(kubectl get pods -n $NAMESPACE -o jsonpath='{.items..metadata.name}' --selector app=$APP) -o json | jq -r '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' | base64 --decode | openssl x509 -text -noout +``` + +In particular look for the following sections: + +``` + Signature Algorithm: ecdsa-with-SHA256 + Issuer: O=cert-manager, O=cluster.local, CN=istio-ca + Validity + Not Before: Jan 13 16:51:59 2022 GMT + Not After : Jan 13 17:51:59 2022 GMT +... + X509v3 Subject Alternative Name: + URI:spiffe://cluster.local/ns/default/sa/httpbin +``` + +You should see the relevant Trust Domain inside the Issuer. In the default case, it should be: +`cluster.local` as above. Note that the SPIFFE URI may be different if you used a different +namespace or application. + +## Clean up + +Assuming your running inside kind, you can simply remove the cluster: + +```shell +kind delete cluster diff --git a/content/v1.12-docs/tutorials/syncing-secrets-across-namespaces.md b/content/v1.12-docs/tutorials/syncing-secrets-across-namespaces.md new file mode 100644 index 0000000000..95e8a6a5fa --- /dev/null +++ b/content/v1.12-docs/tutorials/syncing-secrets-across-namespaces.md @@ -0,0 +1,143 @@ +--- +title: Syncing Secrets Across Namespaces +description: | + Learn how to synchronize Kubernetes Secret resources across namespaces + using extensions such as: reflector, kubed and kubernetes-replicator. +--- + +It may be required for multiple components across namespaces to consume the same +`Secret` that has been created by a single `Certificate`. The recommended way to +do this is to use extensions such as: + - [reflector](https://github.com/emberstack/kubernetes-reflector) with support + for auto secret reflection + - [kubed](https://github.com/appscode/kubed) with its + [secret syncing feature](https://appscode.com/products/kubed/v0.11.0/guides/config-syncer/intra-cluster/) + - [kubernetes-replicator](https://github.com/mittwald/kubernetes-replicator) secret replication + +## Serving a wildcard to ingress resources in different namespaces (default SSL certificate) + +Most ingress controllers, including [ingress-nginx](https://kubernetes.github.io/ingress-nginx/user-guide/tls/#default-ssl-certificate), [Traefik](https://docs.traefik.io/https/tls/#default-certificate), and [Kong](https://docs.konghq.com/2.0.x/configuration/#ssl_cert) support specifying a _single_ certificate to be used for ingress resources which request TLS but do not specify `tls.[].secretName`. This is often referred to as a "default SSL certificate". As long as this is correctly configured, ingress resources in any namespace will be able to use a single wildcard certificate. Wildcard certificates are not supported with HTTP01 validation and require DNS01. + +Sample ingress snippet: + +``` +apiVersion: networking.k8s.io/v1 +kind: Ingress +#[...] +spec: + rules: + - host: service.example.com + #[...] + tls: + - hosts: + - service.example.com + #secretName omitted to use default wildcard certificate +``` + + +## Syncing arbitrary secrets across namespaces using extensions + +In order for the target Secret to be synced, you can use the `secretTemplate` field +for annotating the generated secret with the extension specific annotation (See [CertificateSecretTemplate]). + + +### Using `reflector` + The example below shows syncing a certificate's secret from the `cert-manager` namespace to multiple namespaces (i.e. `dev`, `staging`, `prod`). + Reflector will ensure that any namespace (existing or new) matching the allowed condition (with regex support) will get a copy of the certificate's secret and will keep it up to date. + You can also sync other secrets (different name) using `reflector` (consult the extension's [README](https://github.com/emberstack/kubernetes-reflector/blob/main/README.md)) + +```yaml +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: source + namespace: cert-manager +spec: + secretName: source-tls + commonName: source + issuerRef: + name: source-ca + kind: Issuer + group: cert-manager.io + secretTemplate: + annotations: + reflector.v1.k8s.emberstack.com/reflection-allowed: "true" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "dev,staging,prod" # Control destination namespaces + reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" # Auto create reflection for matching namespaces + reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "dev,staging,prod" # Control auto-reflection namespaces +``` + + +### Using `kubed` + The example below shows syncing +a certificate belonging to the `sandbox` Certificate from the `cert-manager` +namespace, into the `sandbox` namespace. + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: sandbox + labels: + cert-manager-tls: sandbox # Define namespace label for kubed +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: sandbox + namespace: cert-manager +spec: + secretName: sandbox-tls + commonName: sandbox + issuerRef: + name: sandbox-ca + kind: Issuer + group: cert-manager.io + secretTemplate: + annotations: + kubed.appscode.com/sync: "cert-manager-tls=sandbox" # Sync certificate to matching namespaces +``` + + +### Using `kubernetes-replicator` +Replicator supports both push- and pull-based replication. Push-based +replication will "push out" the TLS secret into namespaces when new ones are +created, or when the secret changes. Pull-based replication makes it possible +to create an empty TLS secret in the destination namespace and select a +"source" resource from which the data is replicated from. The following example +shows the pull-based approach: +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: source + namespace: cert-manager +spec: + secretName: source-tls + commonName: source + issuerRef: + name: source-ca + kind: Issuer + secretTemplate: + annotations: + replicator.v1.mittwald.de/replication-allowed: "true" # permit replication + replicator.v1.mittwald.de/replication-allowed-namespaces: "dev,test,prod-[0-9]*" # comma separated list of namespaces or regular expressions +--- +apiVersion: v1 +kind: Secret +metadata: + name: tls-secret-replica + namespace: prod-1 + annotations: + replicator.v1.mittwald.de/replicate-from: cert-manager/source-tls +type: kubernetes.io/tls +# Normally, we'd create an empty destination secret, but secrets of type +# 'kubernetes.io/tls' are treated in a special way and need to have properties +# data["tls.crt"] and data["tls.key"] to begin with, though they may be empty. +data: + tls.key: "" + tls.crt: "" +``` + +[CertificateSecretTemplate]: ../reference/api-docs.md#cert-manager.io/v1.CertificateSecretTemplate diff --git a/content/v1.12-docs/tutorials/venafi/venafi.md b/content/v1.12-docs/tutorials/venafi/venafi.md new file mode 100644 index 0000000000..505276f626 --- /dev/null +++ b/content/v1.12-docs/tutorials/venafi/venafi.md @@ -0,0 +1,586 @@ +--- +title: Securing Ingresses with Venafi +description: 'cert-manager tutorials: Securing Ingress using Venafi Issuers' +--- + +This guide walks you through how to secure a Kubernetes +[`Ingress`](https://kubernetes.io/docs/concepts/services-networking/ingress/) +resource using the Venafi Issuer type. + +Whilst stepping through, you will learn how to: + +- Create an EKS cluster using [`eksctl`](https://github.com/weaveworks/eksctl) +- Install cert-manager into the EKS cluster +- Deploy [`nginx-ingress`](https://github.com/kubernetes/ingress-nginx) to + expose applications running in the cluster +- Configure a Venafi Cloud issuer +- Configure cert-manager to secure your application traffic + +While this guide focuses on EKS as a Kubernetes provisioner and Venafi +as a Certificate issuer, the steps here should be generally re-usable for other +Issuer types. + +## Prerequisites + +- An AWS account +- `kubectl` installed +- Access to a publicly registered DNS zone +- A Venafi Cloud account and API credentials + +## Create an EKS cluster + +If you already have a running EKS cluster you can skip this step and move onto +deploying cert-manager. + +[`eksctl`](https://eksctl.io/introduction/installation/) is a tool that makes it +easier to deploy and manage an EKS cluster. + +Installation instructions for various platforms can be found in the +[`eksctl` installation +instructions](https://eksctl.io/introduction/installation/). + +Once installed, you can create a basic cluster by running: + +``` +$ eksctl create cluster +``` + +This process may take up to 20 minutes to complete. Complete instructions on +using `eksctl` can be found in the [`eksctl` usage +section](https://eksctl.io/usage/creating-and-managing-clusters/). + +Once your cluster has been created, you should verify that your cluster is +running correctly by running the following command: + +``` +$ kubectl get pods --all-namespaces +NAME READY STATUS RESTARTS AGE +aws-node-8xpkp 1/1 Running 0 115s +aws-node-tflxs 1/1 Running 0 118s +coredns-694d9447b-66vlp 1/1 Running 0 23s +coredns-694d9447b-w5bg8 1/1 Running 0 23s +kube-proxy-4dvpj 1/1 Running 0 115s +kube-proxy-tpvht 1/1 Running 0 118s +``` + +You should see output similar to the above, with all pods in a Running state. + +## Installing cert-manager + +There are no special requirements to note when installing cert-manager on EKS, +so the regular [running on +Kubernetes](../../installation/README.md) guides +can be used to install cert-manager. + +Please walk through the installation guide and return to this step once you +have validated cert-manager is deployed correctly. + +## Installing `ingress-nginx` + +A [Kubernetes ingress +controller](https://eksctl.io/usage/creating-and-managing-clusters/) is designed +to be the access point for HTTP and HTTPS traffic to the software running within +your cluster. The [`ingress-nginx`](https://github.com/kubernetes/ingress-nginx) +controller does this by providing an HTTP proxy service supported by your cloud +provider's load balancer (in this case, a [Network Load Balancer +(NLB)](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html). + +You can get more details about `ingress-nginx` and how it works from the +[documentation for `ingress-nginx`](https://kubernetes.github.io/ingress-nginx/). + +To deploy `ingress-nginx` using an ELB to expose the service, run the following: + +Deploy the AWS specific prerequisite manifest +```bash +$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.4.0/deploy/static/provider/aws/deploy.yaml +``` + +Deploy the 'generic' `ingress-nginx` manifest +```bash +$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/aws/deploy.yaml +``` + +You may have to wait up to 5 minutes for all the required components in your +cluster and AWS account to become ready. + +You can run the following command to determine the address that Amazon has +assigned to your NLB: + +```bash +$ kubectl get service -n ingress-nginx +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +ingress-nginx LoadBalancer 10.100.52.175 a8c2870a5a8a311e9a9a10a2e7af57d7-6c2ec8ede48726ab.elb.eu-west-1.amazonaws.com 80:31649/TCP,443:30567/TCP 4m10s +``` + +The *EXTERNAL-IP* field may say `` for a while. This indicates the NLB +is still being created. Retry the command until an *EXTERNAL-IP* has been +provisioned. + +Once the *EXTERNAL-IP* is available, you should run the following command to +verify that traffic is being correctly routed to `ingress-nginx`: + +``` +$ curl http://a8c2870a5a8a311e9a9a10a2e7af57d7-6c2ec8ede48726ab.elb.eu-west-1.amazonaws.com/ + +404 Not Found + +

      404 Not Found

      +
      openresty/1.15.8.1
      + + +``` + +Whilst the above message would normally indicate an error (the page not being +found), in this instance it indicates that traffic is being correctly routed to +the `ingress-nginx` service. + +> Note: Although the AWS Application Load Balancer (ALB) is a modern load +> balancer offered by AWS that can can be provisioned from within EKS, at the +> time of writing, the +> [`alb-ingress-controller`](https://github.com/kubernetes-sigs/aws-alb-ingress-controller>) +> is only capable of serving sites using certificates stored in AWS Certificate +> Manager (ACM). Version 1.15 of Kubernetes should address multiple bug fixes +> for this controller and allow for TLS termination support. + +## Configure your DNS records + +Now that our NLB has been provisioned, we should point our application's DNS +records at the NLBs address. + +Go into your DNS provider's console and set a CNAME record pointing to your +NLB. + +For the purposes of demonstration, we will assume in this guide you have created +the following DNS entry: + +``` +example.com CNAME a8c2870a5a8a311e9a9a10a2e7af57d7-6c2ec8ede48726ab.elb.eu-west-1.amazonaws.com +``` + +As you progress through the rest of this tutorial, please replace `example.com` +with your own registered domain. + +## Deploying a demo application + +For the purposes of this demo, we provide an example deployment which is a +simple "hello world" website. + +First, create a new namespace that will contain your application: + +```bash +$ kubectl create namespace demo +namespace/demo created +``` + +Save the following YAML into a file named `demo-deployment.yaml`: + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: hello-kubernetes + namespace: demo +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 8080 + selector: + app: hello-kubernetes +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hello-kubernetes + namespace: demo +spec: + replicas: 2 + selector: + matchLabels: + app: hello-kubernetes + template: + metadata: + labels: + app: hello-kubernetes + spec: + containers: + - name: hello-kubernetes + image: paulbouwer/hello-kubernetes:1.5 + resources: + requests: + cpu: 100m + memory: 100Mi + ports: + - containerPort: 8080 +``` + +Then run: + +```bash +kubectl apply -n demo -f demo-deployment.yaml +``` + +Note that the Service resource we deploy is of type `ClusterIP` and not +`LoadBalancer`, as we will expose and secure traffic for this service using +`ingress-nginx` that we deployed earlier. + +You should be able to see two Pods and one Service in the `demo` namespace: + +```bash +kubectl get po,svc -n demo +NAME READY STATUS RESTARTS AGE +hello-kubernetes-66d45d6dff-m2lnr 1/1 Running 0 7s +hello-kubernetes-66d45d6dff-qt2kb 1/1 Running 0 7s + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/hello-kubernetes ClusterIP 10.100.164.58 80/TCP 7s +``` + +Note that we have not yet exposed this application to be accessible over the +internet. We will expose the demo application to the internet in later steps. + +## Creating a Venafi Issuer resource + +cert-manager supports both Venafi TPP and Venafi Cloud. + +Please only follow one of the below sections according to where you want to +retrieve your Certificates from. + +### Venafi TPP + +Assuming you already have a Venafi TPP server set up properly, you can create +a Venafi Issuer resource that can be used to issue certificates. + +To do this, you need to make sure you have your TPP *username* and *password*. + +In order for cert-manager to be able to authenticate with your Venafi TPP +server and set up an Issuer resource, you'll need to create a Kubernetes +Secret containing your username and password: + +```bash +$ kubectl create secret generic \ + venafi-tpp-secret \ + --namespace=demo \ + --from-literal=username='YOUR_TPP_USERNAME_HERE' \ + --from-literal=password='YOUR_TPP_PASSWORD_HERE' +``` + +We must then create a Venafi Issuer resource, which represents a certificate +authority within Kubernetes. + +Save the following YAML into a file named `venafi-issuer.yaml`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: venafi-issuer + namespace: demo +spec: + venafi: + zone: "Default" # Set this to the Venafi policy zone you want to use + tpp: + url: https://venafi-tpp.example.com/vedsdk # Change this to the URL of your TPP instance + caBundle: + credentialsRef: + name: venafi-tpp-secret +``` + +Then run: + +```bash +$ kubectl apply -n demo -f venafi-issuer.yaml +``` + +When you run the following command, you should see that the Status stanza of +the output shows that the Issuer is Ready (i.e. has successfully validated +itself with the Venafi TPP server). + +```bash +$ kubectl describe issuer -n demo venafi-issuer + + Status: + Conditions: + Last Transition Time: 2019-07-17T15:46:00Z + Message: Venafi issuer started + Reason: Venafi issuer started + Status: True + Type: Ready + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Ready 14s cert-manager Verified issuer with Venafi server +``` + +### Venafi Cloud + +You can sign up for a Venafi Cloud account by visiting the [enrollment +page](https://www.venafi.com/cloud). + +Once registered, you should fetch your API key by clicking your name in the top +right of the control panel interface. + +In order for cert-manager to be able to authenticate with your Venafi Cloud +account and set up an Issuer resource, you'll need to create a Kubernetes +Secret containing your API key: + +```bash +$ kubectl create secret generic \ + venafi-cloud-secret \ + --namespace=demo \ + --from-literal=apikey= +``` + +We must then create a Venafi Issuer resource, which represents a certificate +authority within Kubernetes. + +Save the following YAML into a file named `venafi-issuer.yaml`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: venafi-issuer + namespace: demo +spec: + venafi: + zone: "Default" # Set this to the Venafi policy zone you want to use + cloud: + apiTokenSecretRef: + name: venafi-cloud-secret + key: apikey +``` + +Then run: + +```bash +$ kubectl apply -n demo -f venafi-issuer.yaml +``` + +When you run the following command, you should see that the Status stanza of +the output shows that the Issuer is Ready (i.e. has successfully validated +itself with the Venafi Cloud service). + +```bash +$ kubectl describe issuer -n demo venafi-issuer +... +Status: + Conditions: + Last Transition Time: 2019-07-17T15:46:00Z + Message: Venafi issuer started + Reason: Venafi issuer started + Status: True + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Ready 14s cert-manager Verified issuer with Venafi server +``` + +## Request a Certificate + +Now that the Issuer is configured and we have confirmed it has been set up +correctly, we can begin requesting certificates which can be used by Kubernetes +applications. + +Full information on how to specify and request Certificate resources can be +found in the [Issuing certificates](../../usage/certificate.md) guide. + +For now, we will create a basic X.509 Certificate that is valid for our domain, +`example.com`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-tls + namespace: demo +spec: + secretName: example-com-tls + dnsNames: + - example.com + commonName: example.com + issuerRef: + name: venafi-issuer +``` + +Save this YAML into a file named `example-com-tls.yaml` and run: + +```bash +$ kubectl apply -n demo -f example-com-tls.yaml +``` + +As long as you've ensured that the zone of your Venafi Cloud account (in our +example, we use the "Default" zone) has been configured with a CA or contains a +custom certificate, cert-manager can now take steps to populate the +`example-com-tls` Secret with a certificate. It does this by identifying itself +with Venafi Cloud using the API key, then requesting a certificate to match the +specifications of the Certificate resource that we've created. + +You can run `kubectl describe` to check the progress of your Certificate: + +```bash +$ kubectl describe certificate -n demo example-com-tls +... +Status: + Conditions: + Last Transition Time: 2019-07-17T17:43:01Z + Message: Certificate is up to date and has not expired + Reason: Ready + Status: True + Type: Ready + Not After: 2019-10-15T12:00:00Z +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Issuing 33s cert-manager Requesting new certificate... + Normal GenerateKey 33s cert-manager Generated new private key + Normal Validate 33s cert-manager Validated certificate request against Venafi zone policy + Normal Requesting 33s cert-manager Requesting certificate from Venafi server... + Normal Retrieve 15s cert-manager Retrieved certificate from Venafi server + Normal CertIssued 15s cert-manager Certificate issued successfully +``` + +Once the Certificate has been issued, you should see events similar to above. + +You should then be able to see the certificate has been successfully stored in +the Secret resource: + +```bash +$ kubectl get secret -n demo example-com-tls +NAME TYPE DATA AGE +example-com-tls kubernetes.io/tls 3 2m47s + +$ kubectl get secret example-com-tls -o 'go-template={{index .data "tls.crt"}}' | \ + base64 --decode | \ + openssl x509 -noout -text +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 0d:ce:bf:89:04:d4:41:83:f4:4c:32:66:64:fb:60:14 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, O=DigiCert Inc, CN=DigiCert Test SHA2 Intermediate CA-1 + Validity + Not Before: Jul 17 00:00:00 2019 GMT + Not After : Oct 15 12:00:00 2019 GMT + Subject: C=US, ST=California, L=Palo Alto, O=Venafi Cloud, OU=SerialNumber, CN=example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:ad:2e:66:02:20:c9:b1:6a:00:63:70:4e:22:3c: + 45:63:6e:e7:fd:4c:94:7d:75:50:22:a2:01:72:99: + 9c:23:04:90:51:85:4d:47:32:e4:8b:ee:b1:ea:09: + 1a:de:97:5d:31:05:a2:73:73:4f:06:a3:b2:59:ee: + bc:30:f7:26:85:3d:b3:56:e4:c2:97:34:b6:ac:6d: + 65:7e:a2:4e:b4:ce:f2:0a:0a:4c:d7:32:d7:5a:18: + e8:69:c6:34:28:26:36:ef:c5:bc:ae:ba:ca:d2:46: + 3f:d4:61:39:66:8f:19:cc:d6:d6:10:77:af:51:93: + 1b:4d:f8:d1:10:19:ab:ac:b3:7b:0b:98:58:29:e6: + a9:ac:9f:7a:dc:63:0d:51:f5:bd:9f:f3:03:2e:b3: + 2d:2f:00:87:f4:e1:cd:5a:32:c6:d8:fb:49:c4:e7: + da:3f:0f:8f:bb:66:94:28:5d:99:fe:7c:f0:17:1b: + fd:3e:ed:dd:36:bf:8e:62:60:0c:85:7f:76:74:4b: + 37:d9:c2:e8:74:49:04:bf:f1:83:81:cc:4f:9b:f3: + 40:97:d4:dc:b6:d3:2d:dc:73:18:93:48:a5:8f:6c: + 57:7f:ec:62:c0:bc:c2:b0:e9:0a:51:2d:c4:b6:87: + 68:96:87:f8:9a:86:3c:6a:f1:01:ca:57:c4:07:e7: + b0:51 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Authority Key Identifier: + keyid:D6:4D:F9:39:60:6C:73:C3:22:F5:AD:30:0C:2F:A0:D5:CA:75:4A:2A + + X509v3 Subject Key Identifier: + A3:B3:47:2C:41:5E:9C:B2:27:97:57:14:A4:2E:BA:8C:93:E7:01:65 + X509v3 Subject Alternative Name: + DNS:example.com + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 CRL Distribution Points: + + Full Name: + URI:http://crl3.digicert.com/DigiCertTestSHA2IntermediateCA1.crl + + Full Name: + URI:http://crl4.digicert.com/DigiCertTestSHA2IntermediateCA1.crl + + X509v3 Certificate Policies: + Policy: 2.16.840.1.114412.1.1 + CPS: https://www.digicert.com/CPS + + Authority Information Access: + OCSP - URI:http://ocsp.digicert.com + CA Issuers - URI:http://cacerts.test.digicert.com/DigiCertTestSHA2IntermediateCA1.crt + + X509v3 Basic Constraints: critical + CA:FALSE + Signature Algorithm: sha256WithRSAEncryption + ae:d4:9c:8a:66:19:9e:7d:12:b7:05:c2:b6:33:b3:9c:a5:40: + 47:ab:34:8d:1b:0f:51:96:de:e9:46:5a:e4:16:10:43:56:bf: + fa:f8:64:f4:cb:53:39:5b:45:ca:7f:15:d9:59:25:21:23:c4: + 4d:dc:a7:f7:83:21:d2:3f:a8:0a:26:f4:ef:fa:1b:2b:7d:97: + 7e:28:f3:ca:cd:b2:c4:92:f3:92:27:7f:e0:f1:ac:d6:db:4c: + 10:8a:f8:6f:09:bb:b3:4f:19:06:aa:bb:74:1c:e0:51:42:f6: + 8c:7d:77:f7:80:a4:03:ab:a9:ae:ae:2b:89:17:af:2f:eb:f7: + 3d:61:7c:dd:e1:5d:d2:5a:c5:6a:f6:c8:92:4c:0a:b5:75:d1: + dd:39:f2:a7:a2:10:8c:6d:bf:ca:08:ad:b9:a9:df:e3:59:8f: + 64:16:3c:7e:8a:6e:27:fc:49:d7:06:f0:bd:94:15:f2:fd:0f: + 94:8a:b8:73:67:73:53:22:df:9d:36:e9:34:f9:2a:68:00:59: + 78:6d:2d:8f:a0:0f:13:af:bd:b3:aa:8c:37:c4:22:cf:23:fb: + 56:bc:4e:55:ae:3a:0a:e6:3e:b1:1a:22:71:7b:08:b8:00:41: + 14:26:f6:9b:9b:72:3f:eb:dc:dd:1b:db:a8:20:fd:54:75:ae: + 25:7f:80:e6 +``` + +In the next step, we'll configure your application to actually use this new +Certificate resource. + +## Exposing and securing your application + +Now that we have issued a Certificate, we can expose our application using a +Kubernetes Ingress resource. + +Create a file named `application-ingress.yaml` and save the following in it, +replacing `example.com` with your own domain name: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: frontend-ingress + namespace: demo + annotations: + kubernetes.io/ingress.class: "nginx" +spec: + tls: + - hosts: + - example.com + secretName: example-com-tls + rules: + - host: example.com + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: kuard + port: + number: 80 +``` + +You can then apply this resource with: + +```bash +$ kubectl apply -n demo -f application-ingress.yaml +``` + +Once this has been created, you should be able to visit your application at the +configured URL, here `example.com`! + +Navigate to the address in your web browser and you should see the certificate +obtained via Venafi being used to secure application traffic. diff --git a/content/v1.12-docs/tutorials/zerossl/zerossl.md b/content/v1.12-docs/tutorials/zerossl/zerossl.md new file mode 100644 index 0000000000..e10d997ec9 --- /dev/null +++ b/content/v1.12-docs/tutorials/zerossl/zerossl.md @@ -0,0 +1,180 @@ +--- +title: "Securing Ingresses with ZeroSSL" +linkTitle: "Securing Ingresses with ZeroSSL" +--- + +# The ZeroSSL + +This guide walks you through how to secure a Kubernetes [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) resource using the ZeroSSL Issuer type. + +The ZeroSSL just like Let's Encrypt and its competitors allows to create free 90 days certificates. All is need is to create account at https://zerossl.com/. After that go to developer section and generate `EAB Credentials for ACME Clients`. You will need it later. + + +`Please note!` \ +EAB credentials are not stored in your account, please make sure to note them somewhere. Each click on "Generate" will create a new set of credentials. Even if you create multiple credentials, all of them will remain functional. + + + +# Prerequisites + +- An AWS account +- kubectl installed +- Access to a publicly registered DNS zone +- Kubernetes cluster, you can use AWS EKS +- [ingress-nginx](https://kubernetes.github.io/ingress-nginx/) deployed and working inside cluster + + +# Tutorial scenario: + +## Installing cert-manager + +Make sure you use cert-manager `1.8.2+`/`1.7.3+`. See [link](https://github.com/cert-manager/cert-manager/pull/5226) for more details. + +Please walk through the installation guide and return to this step once you +have validated cert-manager is deployed correctly. Follow steps under [running on +Kubernetes](../../installation/helm.md) to install in k8s. + +In order to automatically switch to the ZeroSSL we recommend setting default shim by adding the following configuration to values file. + +```yaml +ingressShim: + defaultIssuerName: "zerossl-production" + defaultIssuerKind: "ClusterIssuer" + +installCRDs: true +``` + +Install it using helm: +``` +helm upgrade --install --namespace cert-manager --version v1.8.2 cert-manager jetstack/cert-manager -f values.yaml +``` + +## Configure your DNS records + +The best way to manage DNS using AWS is by using Route53. Create AWS account with permissions to modify Route53 rules. + +## EAB secret +Once you will get your credentials first step is to create seed with secrets. They are responsible for authenticating with your ZeroSSL account. + +```bash +$ kubectl create secret generic \ + zero-ssl-eabsecret \ + --namespace=cert-manager \ + --from-literal=secret='YOUR_ZEROSSL_EAB_HMAC_KEY' +``` + +### Another way of creating secret. + +Encode it in base64 first. +```bash +echo -n "YOUR_ZEROSSL_EAB_HMAC_KEY" | base64 -w 0 +``` + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: zero-ssl-eabsecret +data: + secret: YOUR_ENCODED_ZEROSSL_EAB_HMAC_KEY +``` +```bash +kubectl apply -f zero-ssl-eabsecret.yaml -n cert-manager +``` + +## Cluster issuer +Then we must create the `ZeroSSL` `ClusterIssuer`, let's call it `zerossl-production`. In our case we are using AWS. See pre-conditions to provision all required elements. + +```yaml +apiVersion: cert-manager.io/v1alpha2 +kind: ClusterIssuer +metadata: + name: zerossl-production +spec: + acme: + # ZeroSSL ACME server + server: https://acme.zerossl.com/v2/DV90 + email: dummy-email@yopmail.com + + # name of a secret used to store the ACME account private key + privateKeySecretRef: + name: zerossl-prod + + # for each cert-manager new EAB credencials are required + externalAccountBinding: + keyID: YOUR_ZEROSSL_EAB_KEY_ID + keySecretRef: + name: zero-ssl-eabsecret + key: secret + keyAlgorithm: HS256 + + # ACME DNS-01 provider configurations to verify domain + solvers: + - selector: {} + dns01: + route53: + region: us-west-2 + # optional if ambient credentials are available; see ambient credentials documentation + # see Route53 for >0 issue "letsencrypt.org" and change to >0 issue "sectigo.com" + accessKeyID: ACCESS_KEY_ID + secretAccessKeySecretRef: + name: route53-credentials-secret + key: secret-access-key + +``` + +### Then run: + +```bash +$ kubectl apply -n cert-manager -f zerossl-production.yaml +``` + +```bash +$ kubectl describe Clusterissuer zerossl-prod + +Status: + Acme: + Last Registered Email: dummy-email@yopmail.com + Uri: https://acme.zerossl.com/v2/DV90/account/tXXX_NwSv15rlS_XXXX + Conditions: + Last Transition Time: 2021-09-09T17:03:26Z + Message: The ACME account was registered with the ACME server + Reason: ACMEAccountRegistered + Status: True + Type: Ready +``` + +### Please note! +If this step failed and the ACME account is not registered please check if secrets in `zero-ssl-eabsecret` are correct. + +## Request a ingress certificate + + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: test-ingress + namespace: default +spec: + rules: + - host: test.example.com + tls: + - secretName: secret-tls + +``` + +Apply test-ingress: + +```bash +kubectl apply -f ingress.yaml +``` + +You are set! Check your ingress. +```bash +kubectl describe ingress test-ingress -n default +# check if tls is terminated using secret-tls + +openssl s_client -showcerts -connect test.example.com:443 +# verify server certificate and its chain +``` diff --git a/content/v1.12-docs/usage/README.md b/content/v1.12-docs/usage/README.md new file mode 100644 index 0000000000..b0d93801a9 --- /dev/null +++ b/content/v1.12-docs/usage/README.md @@ -0,0 +1,31 @@ +--- +title: Issuing Certificates +description: 'cert-manager usage: Overview' +--- + +Once an [`Issuer`](../configuration/README.md) has been configured, you're ready to issue your first certificate! + +There are several use cases and methods for requesting certificates through cert-manager: + +- [Certificate Resources](./certificate.md): The simplest and most common method for + requesting signed certificates. +- [Securing Ingress Resources](./ingress.md): A method to secure ingress resources + in your cluster. +- [Securing OpenFaaS functions](https://docs.openfaas.com/reference/ssl/kubernetes-with-cert-manager/): + Secure your OpenFaaS services using cert-manager. +- [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a + developer tool for developing Kubernetes applications which has first class + support for integrating cert-manager. +- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure + your Knative services with trusted HTTPS certificates. +- [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI + driver to provide unique keys and certificates that share the lifecycle of + pods. +- [Securing Istio Gateway](https://istio.io/docs/tasks/traffic-management/ingress/ingress-certmgr/): + Secure your Istio Gateway in Kubernetes using cert-manager. +- [Securing Istio Service Mesh](./istio.md): Using the cert-manager + [Istio](https://istio.io) integration, secure the mTLS PKI for each pod + through cert-manager managed certificates. +- [Policy for cert-manager certificates](./approver-policy.md): Manage + what cert-manager certificates are able to be signed or rejected through + custom resource defined policy. diff --git a/content/v1.12-docs/usage/approver-policy.md b/content/v1.12-docs/usage/approver-policy.md new file mode 100644 index 0000000000..b712d04b7f --- /dev/null +++ b/content/v1.12-docs/usage/approver-policy.md @@ -0,0 +1,14 @@ +--- +title: Policy for cert-manager certificates +description: 'cert-manager usage: approver-policy' +--- + +cert-manager [CertificateRequests](../concepts/certificaterequest.md) can be +rejected from being signed by using the [approval +API](../concepts/certificaterequest.md#approval). +[approver-policy](https://github.com/cert-manager/approver-policy) is a +cert-manager project that enables you to write policy to automatically manage +this approval mechanism. + +Please read the [project page](../projects/approver-policy/README.md) for more +information on how to install and use approver-policy. diff --git a/content/v1.12-docs/usage/certificate.md b/content/v1.12-docs/usage/certificate.md new file mode 100644 index 0000000000..17792853bf --- /dev/null +++ b/content/v1.12-docs/usage/certificate.md @@ -0,0 +1,368 @@ +--- +title: Certificate Resources +description: 'cert-manager usage: Certificates' +--- + +In cert-manager, the [`Certificate`](../concepts/certificate.md) resource +represents a human readable definition of a certificate request that is to be +honored by an issuer which is to be kept up-to-date. This is the usual way that +you will interact with cert-manager to request signed certificates. + +In order to issue any certificates, you'll need to configure an +[`Issuer`](../configuration/README.md) or [`ClusterIssuer`](../configuration/README.md) +resource first. + +## Creating Certificate Resources + +A `Certificate` resource specifies fields that are used to generate certificate +signing requests which are then fulfilled by the issuer type you have +referenced. `Certificates` specify which issuer they want to obtain the +certificate from by specifying the `certificate.spec.issuerRef` field. + +A `Certificate` resource, for the `example.com` and `www.example.com` DNS names, +`spiffe://cluster.local/ns/sandbox/sa/example` URI Subject Alternative Name, +that is valid for 90 days and renews 15 days before expiry is below. It contains +an exhaustive list of all options a `Certificate` resource may have however only +a subset of fields are required as labelled. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: sandbox +spec: + # Secret names are always required. + secretName: example-com-tls + + # secretTemplate is optional. If set, these annotations and labels will be + # copied to the Secret named example-com-tls. These labels and annotations will + # be re-reconciled if the Certificate's secretTemplate changes. secretTemplate + # is also enforced, so relevant label and annotation changes on the Secret by a + # third party will be overwriten by cert-manager to match the secretTemplate. + secretTemplate: + annotations: + my-secret-annotation-1: "foo" + my-secret-annotation-2: "bar" + labels: + my-secret-label: foo + + duration: 2160h # 90d + renewBefore: 360h # 15d + subject: + organizations: + - jetstack + # The use of the common name field has been deprecated since 2000 and is + # discouraged from being used. + commonName: example.com + isCA: false + privateKey: + algorithm: RSA + encoding: PKCS1 + size: 2048 + usages: + - server auth + - client auth + # At least one of a DNS Name, URI, or IP address is required. + dnsNames: + - example.com + - www.example.com + uris: + - spiffe://cluster.local/ns/sandbox/sa/example + ipAddresses: + - 192.168.0.5 + # Issuer references are always required. + issuerRef: + name: ca-issuer + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: Issuer + # This is optional since cert-manager will default to this value however + # if you are using an external issuer, change this to that issuer group. + group: cert-manager.io +``` + +The signed certificate will be stored in a `Secret` resource named +`example-com-tls` in the same namespace as the `Certificate` once the issuer has +successfully issued the requested certificate. + +If `secretTemplate` is present, annotations and labels set in this property +will be copied over to `example-com-tls` secret. Both properties are optional. + +The `Certificate` will be issued using the issuer named `ca-issuer` in the +`sandbox` namespace (the same namespace as the `Certificate` resource). + +> Note: If you want to create an `Issuer` that can be referenced by +> `Certificate` resources in _all_ namespaces, you should create a +> [`ClusterIssuer`](../concepts/issuer.md#namespaces) resource and set the +> `certificate.spec.issuerRef.kind` field to `ClusterIssuer`. + +> Note: The `renewBefore` and `duration` fields must be specified using a [Go +> `time.Duration`](https://golang.org/pkg/time/#ParseDuration) string format, +> which does not allow the `d` (days) suffix. You must specify these values +> using `s`, `m`, and `h` suffixes instead. Failing to do so without installing +> the [`webhook component`](../concepts/webhook.md) can prevent cert-manager +> from functioning correctly +> [`#1269`](https://github.com/cert-manager/cert-manager/issues/1269). + +> Note: Take care when setting the `renewBefore` field to be very close to the +> `duration` as this can lead to a renewal loop, where the `Certificate` is always +> in the renewal period. Some `Issuers` set the `notBefore` field on their +> issued X.509 certificates before the issue time to fix clock-skew issues, +> leading to the working duration of a certificate to be less than the full +> duration of the certificate. For example, Let's Encrypt sets it to be one hour +> before issue time, so the actual _working duration_ of the certificate is 89 +> days, 23 hours (the _full duration_ remains 90 days). + +A full list of the fields supported on the Certificate resource can be found in +the [API reference documentation](../reference/api-docs.md#cert-manager.io/v1.CertificateSpec). + +

      X.509 key usages and extended key usages

      + +cert-manager supports requesting certificates that have a number of [custom key +usages](https://tools.ietf.org/html/rfc5280#section-4.2.1.3) and [extended key +usages](https://tools.ietf.org/html/rfc5280#section-4.2.1.12). Although +cert-manager will attempt to honor this request, some issuers will remove, add +defaults, or otherwise completely ignore the request. +The `CA` and `SelfSigned` `Issuer` will always return certificates matching the usages you have requested. + +Unless any number of usages has been set, cert-manager will set the default +requested usages of `digital signature`, `key encipherment`, and `server auth`. +cert-manager will not attempt to request a new certificate if the current +certificate does not match the current key usage set. + +An exhaustive list of supported key usages can be found in the [API reference +documentation](../reference/api-docs.md#cert-manager.io/v1.KeyUsage). + +

      Temporary Certificates while Issuing

      + +When requesting certificates [using the ingress-shim](./ingress.md), the +component `ingress-gce`, if used, requires that a temporary certificate is +present while waiting for the issuance of a signed certificate when serving. To +facilitate this, if the following annotation: + + ```yaml + cert-manager.io/issue-temporary-certificate: "true" + ``` + +is present on the certificate, a self-signed temporary certificate will be +present on the `Secret` until it is overwritten once the signed certificate has +been issued. + +Adding the following annotation on an ingress will automatically set "issue-temporary-certificate" on the certificate: + + ```yaml + acme.cert-manager.io/http01-edit-in-place: "true" + ``` + +

      Rotation of the private key

      + +By default, the private key won't be rotated automatically. Using the setting +`rotationPolicy: Always`, the private key Secret associated with a Certificate +object can be configured to be rotated as soon as an action triggers the +reissuance of the Certificate object (see +[Actions that will trigger a rotation of the private key](#actions-triggering-private-key-rotation) below). + +With `rotationPolicy: Always`, cert-manager waits until the Certificate +object is correctly signed before overwriting the `tls.key` file in the +Secret. + +With this setting, you can expect **no downtime** if your application can detect +changes to the mounted `tls.crt` and `tls.key` and reload them gracefully or +automatically restart. + +If your application only loads the private key and signed certificate once +at start up, the new certificate won't immediately be served by your +application, and you will want to either manually restart your pod with +`kubectl rollout restart`, or automate the action by running +[wave](https://github.com/wave-k8s/wave). Wave is a Secret controller that +makes sure deployments get restarted whenever a mounted Secret changes. + +
      + +Re-use of private keys + +Some issuers, like the built-in [Venafi +issuer](../configuration/venafi.md), may disallow re-using private keys. +If this is the case, you must explicitly configure the `rotationPolicy: +Always` setting for each of your Certificate objects accordingly. + +
      + +In the following example, the certificate has been set with +`rotationPolicy: Always`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +spec: + secretName: my-cert-tls + privateKey: + rotationPolicy: Always # 🔰 Here. +``` + +

      Actions that will trigger a rotation of the private key

      + +Setting the `rotationPolicy: Always` won't rotate the private key immediately. +In order to rotate the private key, the certificate objects must be reissued. A +certificate object is reissued under the following circumstances: + +- when the X.509 certificate is nearing expiry, which is when the Certificate's + `status.renewalTime` is reached; +- when a change is made to one of the following fields on the Certificate's + spec: `commonName`, `dnsNames`, `ipAddresses`, `uris`, `emailAddresses`, + `subject`, `isCA`, `usages`, `duration` or `issuerRef`; +- when a reissuance is manually triggered with the following: + ```sh + cmctl renew cert-1 + ``` + Note that the above command requires [cmctl](../reference/cmctl.md#renew). + +
      + +**❌** Deleting the Secret resource associated with a Certificate resource is +**not a recommended solution** for manually rotating the private key. The +recommended way to manually rotate the private key is to trigger the reissuance +of the Certificate resource with the following command (requires +[`cmctl`](../reference/cmctl.md#renew)): + +```sh +cmctl renew cert-1 +``` + +
      + +### The `rotationPolicy` setting + +The possible values for `rotationPolicy` are: + +| Value | Description | +| ---------------------- | ------------------------------------------------------------- | +| `Never` (default) | cert-manager reuses the existing private key on each issuance | +| `Always` (recommended) | cert-manager regenerates a new private key on each issuance | + +With `rotationPolicy: Never`, a private key is only generated if one does not +already exist in the target Secret resource (using the `tls.key` key). All +further issuances will re-use this private key. This is the default in order to +maintain compatibility with previous releases. + +With `rotationPolicy: Always`, a new private key will be generated each time an +action triggers the reissuance of the certificate object (see [Actions that will +trigger a rotation of the private key](#actions-triggering-private-key-rotation) +above). Note that if the private key secret already exists when creating the +certificate object, the existing private key will not be used, since the +rotation mechanism also includes the initial issuance. + +
      + +👉 We recommend that you configure `rotationPolicy: Always` on your Certificate +resources. Rotating both the certificate and the private key simultaneously +prevents the risk of issuing a certificate with an exposed private key. Another +benefit to renewing the private key regularly is to let you be confident that +the private key rotation can be done in case of emergency. More generally, it is +a good practice to be rotating the keys as often as possible, reducing the risk +associated with compromised keys. + +
      + +## Cleaning up Secrets when Certificates are deleted + +By default, cert-manager does not delete the `Secret` resource containing the signed certificate when the corresponding `Certificate` resource is deleted. +This means that deleting a `Certificate` won't take down any services that are currently relying on that certificate, but the certificate will no longer be renewed. +The `Secret` needs to be manually deleted if it is no longer needed. + +If you would prefer the `Secret` to be deleted automatically when the `Certificate` is deleted, you need to configure your installation to pass the `--enable-certificate-owner-ref` flag to the controller. + +## Renewal + +cert-manager will automatically renew `Certificate`s. It will calculate _when_ to renew a `Certificate` based on the issued X.509 certificate's duration and a 'renewBefore' value which specifies _how long_ before expiry a certificate should be renewed. + +`spec.duration` and `spec.renewBefore` fields on a `Certificate` can be used to specify an X.509 certificate's duration and a 'renewBefore' value. Default value for `spec.duration` is 90 days. Some issuers might be configured to only issue certificates with a set duration, so the actual duration may be different. +Minimum value for `spec.duration` is 1 hour and minimum value for `spec.renewBefore` is 5 minutes. +It is also required that `spec.duration` > `spec.renewBefore`. + +Once an X.509 certificate has been issued, cert-manager will calculate the renewal time for the `Certificate`. By default this will be 2/3 through the X.509 certificate's duration. If `spec.renewBefore` has been set, it will be `spec.renewBefore` amount of time before expiry. cert-manager will set `Certificate`'s `status.RenewalTime` to the time when the renewal will be attempted. + +## Additional Certificate Output Formats + +
      + +⛔️ The additional certificate output formats feature is currently in an +_experimental_ alpha state, and is subject to breaking changes or complete +removal in future releases. This feature is only enabled by adding it to the +`--feature-gates` flag on the cert-manager controller and webhook components: + +```bash +--feature-gates=AdditionalCertificateOutputFormats=true +``` + +
      + +`additionalOutputFormats` is a field on the Certificate `spec` that allows +specifying additional supplementary formats of issued certificates and their +private key. There are currently two supported additional output formats: +`CombinedPEM` and `DER`. Both output formats can be specified on the same +Certificate. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +spec: + ... + secretName: my-cert-tls + additionalOutputFormats: + - type: CombinedPEM + - type: DER + +# Results in: + +apiVersion: v1 +kind: Secret +metadata: + name: my-cert-tls +type: kubernetes.io/tls +data: + ca.crt: + tls.key: + tls.crt: + tls-combined.pem: + key.der: +``` + +#### `CombinedPEM` + +The `CombinedPEM` type will create a new key entry in the resulting +Certificate's Secret `tls-combined.pem`. This entry will contain the PEM encoded +private key, followed by at least one new line character, followed by the PEM +encoded signed certificate chain- + +```text + + "\n" + +``` + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-cert-tls +type: kubernetes.io/tls +data: + tls-combined.pem: + ... +``` + +#### `DER` + +The `DER` type will create a new key entry in the resulting Certificate's Secret +`key.der`. This entry will contain the DER binary format of the private key. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-cert-tls +type: kubernetes.io/tls +data: + key.der: + ... +``` diff --git a/content/v1.12-docs/usage/csi.md b/content/v1.12-docs/usage/csi.md new file mode 100644 index 0000000000..5e1a6124b0 --- /dev/null +++ b/content/v1.12-docs/usage/csi.md @@ -0,0 +1,85 @@ +--- +title: CSI Driver +description: 'cert-manager usage: CSI driver' +--- + +## Enabling mTLS of Pods using the cert-manager CSI Driver + +A [Container Storage Interface (CSI) +driver](../projects/csi-driver.md) has been created to +facilitate mTLS of Pods running inside your cluster through use of cert-manager. +Using this driver will ensure that the private key and corresponding signed +certificate will be unique to each Pod and will be stored on disk to the node +that the Pod is scheduled to. The life cycle of the certificate key pair matches +that of the Pod meaning that they will be created at Pod creation, and destroyed +during termination. This driver also handles renewal on live certificates on the +fly. + +A [CSI +driver](https://github.com/container-storage-interface/spec/blob/master/spec.md) +is a storage plugin that is deployed into your Kubernetes cluster that can +honor volume requests specified on Pods, just like those enabled by default such as +the `Secret`, `ConfigMap`, or `hostPath` volume drivers. In the case of the cert-manager +CSI driver, it makes use of the ephemeral volume type, made beta as of +[`v1.16`](https://kubernetes.io/docs/concepts/storage/volumes/#csi-ephemeral-volumes) +and as such will only work from the Kubernetes version `v1.16`. An ephemeral +volumes means that the volume is created and destroyed as the Pod is created and +terminated, as well as specifying the volume attributes, without the need of a +`PersistentVolume`. This gives the feature of not only having unique +certificates and keys per Pod, where the private key never leaves the hosts +node, but that the desired certificate for that Pod template can be defined in +line with the deployment spec. + +> **Warning**: Use of the CSI driver is mostly intended for supporting a PKI of +> your cluster and facilitating mTLS, and as such, a private Certificate +> Authority issuer should be used - CA, Vault, and perhaps Venafi, or other +> external issuers. It is *not* recommended to use public Certificate +> Authorities, for example Let's Encrypt, which hold strict rate limits on the +> number of certificates that can be issued for a single domain. Like Pods, +> these certificate key pairs are designed to be non-immutable and can be +> created and destroyed at any time during normal operation. + +## How Does it Work? + +The CSI specification is a protocol and standard for building storage drivers +for container orchestration platforms with the intention that a single driver +may be ported across multiple platforms and outlines a consistent specification +to how drivers should behave from an infrastructure perspective. Since +cert-manager is designed to only be run with a Kubernetes cluster, so too does +the cert-manager CSI driver. + +The driver should be deployed as a +[DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) +which means a single instance of the driver may be run on each node. The driver +will not work when running multiple instances on a single node. The set of nodes +that the driver runs on can be restricted using the +[`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/) +in its Pod template. + +When a Pod is scheduled to a node with a cert-manager CSI volume specified, the +[`Kubelet`](https://kubernetes.io/docs/concepts/overview/components/#kubelet) +running on that node will send a `NodePublishVolume` call to the driver on that +node, containing that Pods information as well as the attributes detailed from +the in-line volume attributes. From this, the driver will generate a private key +as well as a certificate request based upon that key using information built +from the volume attributes. The driver will create a `CertificateRequest` +resource in the same namespace in the Pod that, if valid, cert-manager will +return a signed certificate. + +The resulting signed certificate and generated key will be written to that +node's file system to be mounted to the Pods file system. Since the driver needs +access to the nodes file system it must be made privileged. Once mounted, the +Pod will begin execution with the unique private key and certificate available in +its file system, as defined by its mount path. + +By default, the driver will keep track of certificates created in order to +monitor when they should be marked for renewal. When this happens, the driver +will request for a new signed certificate, and when successful, will simply +overwrite the existing certificate in path. + +When the Pod is marked for termination, the `NodeUnpublishVolume` call is made +to the node's driver which in turn destroys the certificate and key from the +nodes file system. + +The CSI driver is able to recover its full state in the event the its Pod being +terminated. diff --git a/content/v1.12-docs/usage/gateway.md b/content/v1.12-docs/usage/gateway.md new file mode 100644 index 0000000000..9aa05cf1a1 --- /dev/null +++ b/content/v1.12-docs/usage/gateway.md @@ -0,0 +1,402 @@ +--- +title: Securing gateway.networking.k8s.io Gateway Resources +description: 'cert-manager usage: Kubernetes Gateways' +--- + +**FEATURE STATE**: cert-manager 1.5 [alpha] + +
      + +📌 This page focuses on automatically creating Certificate resources by +annotating Kubernetes Gateway resource. If you are looking for using an ACME Issuer along +with HTTP-01 challenges using the Kubernetes Gateway API, see [ACME +HTTP-01](../configuration/acme/http01/README.md). + +
      + +
      + +🚧 cert-manager 1.8+ is tested with v1alpha2 Kubernetes Gateway API. It should also work +with v1beta1 because of resource conversion, but has not been tested with it. + +
      + +cert-manager can generate TLS certificates for Gateway resources. This is +configured by adding annotations to a Gateway and is similar to the process for +[Securing Ingress Resources](../usage/ingress.md). + +The Gateway resource is part of the [Gateway API][gwapi], a set of CRDs that you +install on your Kubernetes cluster and which provide various improvements over +the Ingress API. + +[gwapi]: https://gateway-api.sigs.k8s.io + +The Gateway resource holds the TLS configuration, as illustrated in the +following diagram (source: https://gateway-api.sigs.k8s.io): + +![Gateway vs. HTTPRoute](/images/gateway-roles.png) + +
      + +📌 This feature requires the installation of the [Gateway API bundle](https://gateway-api.sigs.k8s.io/guides/#installing-a-gateway-controller) and passing a +feature flag to the cert-manager controller. + +To install v1.5.1 Gateway API bundle (Gateway CRDs and webhook), run the following command: + +```sh +kubectl apply -f "https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.5.1/standard-install.yaml" +``` + +To enable the feature in cert-manager, turn on the `GatewayAPI` feature gate: + +- If you are using Helm: + + ```sh + helm upgrade --install cert-manager jetstack/cert-manager --namespace cert-manager \ + --set "extraArgs={--feature-gates=ExperimentalGatewayAPISupport=true}" + ``` + +- If you are using the raw cert-manager manifests, add the following flag to the + cert-manager controller Deployment: + + ```yaml + args: + - --feature-gates=ExperimentalGatewayAPISupport=true + ``` + +The Gateway API CRDs should either be installed before cert-manager starts or +the cert-manager Deployment should be restarted after installing the Gateway API +CRDs. This is important because some of the cert-manager components only perform +the Gateway API check on startup. You can restart cert-manager with the +following command: + +```sh +kubectl rollout restart deployment cert-manager -n cert-manager +``` + +
      + +The annotations `cert-manager.io/issuer` or `cert-manager.io/cluster-issuer` +tell cert-manager to create a Certificate for a Gateway. For example, the +following Gateway will trigger the creation of a Certificate with the name +`example-com-tls`: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: Gateway +metadata: + name: example + annotations: + cert-manager.io/issuer: foo +spec: + gatewayClassName: foo + listeners: + - name: http + hostname: example.com + port: 443 + protocol: HTTPS + allowedRoutes: + namespaces: + from: All + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls +``` + +A few moments later, cert-manager will create a Certificate. The Certificate is +named after the Secret name `example-com-tls`. The `dnsNames` field is set with +the `hostname` field from the Gateway spec. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-tls +spec: + issuerRef: + name: my-issuer + kind: Issuer + group: cert-manager.io + dnsNames: + - example.com # ✅ Copied from the `hostname` field. + secretName: example-com-tls +``` + +
      + +🚧 this mechanism can only be used to create Secrets in the same namespace as the `Gateway`, see [`cert-manager#5610`](https://github.com/cert-manager/cert-manager/issues/5610) + +
      + +## Use cases + +### Generate TLS certs for selected TLS blocks + +cert-manager skips any listener block that cannot be used for generating a +Certificate. For a listener block to be used for creating a Certificate, it must +meet the following requirements: + +| Field | Requirement | +|--------------------------------|-------------------------------------------------------------| +| `tls.hostname` | Must not be empty. | +| `tls.mode` | Must be set to `Terminate`. `Passthrough` is not supported. | +| `tls.certificateRef.name` | Cannot be left empty. | +| `tls.certificateRef.kind` | If specified, must be set to `Secret`. | +| `tls.certificateRef.group` | If specified, must be set to `core`. | +| `tls.certificateRef.namespace` | If specified, must be the same as the `Gateway`. | + +In the following example, the first four listener blocks will not be used to +generate Certificate resources: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: Gateway +metadata: + name: my-gateway + namespace: default + annotations: + cert-manager.io/issuer: my-issuer +spec: + listeners: + # ❌ Missing "tls" block, the following listener is skipped. + - hostname: example.com + + # ❌ Missing "hostname", the following listener is skipped. + - tls: + certificateRefs: + - name: example-com-tls + kind: Secret" + group: core + + # ❌ "mode: Passthrough" is not supported, the following listener is skipped. + - hostname: example.com + tls: + mode: Passthrough + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + + # ❌ Cross-namespace secret references are not supported, the following listener is skipped. + - hostname: foo.example.com + port: 443 + protocol: HTTPS + allowedRoutes: + namespaces: + from: All + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + namespace: other-namespace + + # ✅ The following listener is valid. + - hostname: foo.example.com # ✅ Required. + port: 443 + protocol: HTTPS + allowedRoutes: + namespaces: + from: All + tls: + mode: Terminate # ✅ Required. "Terminate" is the only supported mode. + certificateRefs: + - name: example-com-tls # ✅ Required. + kind: Secret # ✅ Required. "Secret" is the only valid value. + group: core # ✅ Required. "core" is the only valid value. +``` + +cert-manager has skipped over the first four listener blocks and has created a +single Certificate named `example-com-tls` for the last listener block: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-tls +spec: + issuerRef: + name: my-issuer + kind: Issuer + group: cert-manager.io + dnsNames: + - foo.example.com + secretName: example-com-tls +``` + +### Two listeners with the same Secret name + +The same Secret name can be re-used in multiple TLS blocks, regardless of the +hostname. Let us imagine that you have these two listeners: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: Gateway +metadata: + name: example + annotations: + cert-manager.io/issuer: my-issuer +spec: + gatewayClassName: foo + listeners: + # Listener 1. + - hostname: example.com + port: 443 + protocol: HTTPS + routes: + kind: HTTPRoute + parentRefs: + - name: example + kind: Gateway + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + + # Listener 2: Same Secret name as Listener 1, with a different hostname. + - hostname: *.example.com + port: 443 + protocol: HTTPS + routes: + kind: HTTPRoute + parentRefs: + - name: example + kind: Gateway + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + + # Listener 3: also same Secret name, except the hostname is also the same. + - hostname: *.example.com + port: 8443 + protocol: HTTPS + routes: + kind: HTTPRoute + parentRefs: + - name: example + kind: Gateway + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + + # Listener 4: different Secret name. + - hostname: site.org + port: 443 + protocol: HTTPS + routes: + kind: HTTPRoute + parentRefs: + - name: example + kind: Gateway + tls: + mode: Terminate + certificateRefs: + - name: site-org-tls + kind: Secret + group: core +``` + +cert-manager will create two Certificates since two Secret names are used: +`example-com-tls` and `site-org-tls`. Note the Certificate's `dnsNames` contains +a single occurrence of `*.example.com ` for both listener 2 and 3 (the +`hostname` values are de-duplicated). + +The two Certificates look like this: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-tls +spec: + issuerRef: + name: my-issuer + kind: Issuer + group: cert-manager.io + dnsNames: + - example.com # From listener 1. + - *.example.com # From listener 2 and 3. + secretName: example-com-tls +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: site-org-tls +spec: + issuerRef: + name: my-issuer + kind: Issuer + group: cert-manager.io + dnsNames: + - site.org # From listener 4. + secretName: site-org-tls +``` + +## Supported Annotations + +If you are migrating to Gateway resources from Ingress resources, be aware that +there are some differences between [the annotations for Ingress resources](./ingress.md#supported-annotations) +versus the annotations for Gateway resources. + +The Gateway resource supports the following annotations for generating +Certificate resources: + +- `cert-manager.io/issuer`: the name of an Issuer to acquire the certificate + required for this Gateway. The Issuer _must_ be in the same namespace as the + Gateway resource. + +- `cert-manager.io/cluster-issuer`: the name of a ClusterIssuer to acquire the + Certificate required for this Gateway. It does not matter which namespace your + Gateway resides, as `ClusterIssuers` are non-namespaced resources. + +- `cert-manager.io/issuer-kind`: the kind of the external issuer resource, for + example `AWSPCACIssuer`. This is only necessary for out-of-tree issuers. + +- `cert-manager.io/issuer-group`: the API group of the external issuer + controller, for example `awspca.cert-manager.io`. This is only necessary for + out-of-tree issuers. + +- `cert-manager.io/common-name`: (optional) this annotation allows you to + configure `spec.commonName` for the Certificate to be generated. + +- ` cert-manager.io/duration`: (optional) this annotation allows you to + configure `spec.duration` field for the Certificate to be generated. + +- `cert-manager.io/renew-before`: (optional) this annotation allows you to + configure `spec.renewBefore` field for the Certificate to be generated. + +- `cert-manager.io/usages`: (optional) this annotation allows you to configure + `spec.usages` field for the Certificate to be generated. Pass a string with + comma-separated values i.e "key agreement,digital signature, server auth" + +- `cert-manager.io/revision-history-limit`: (optional) this annotation allows you to + configure `spec.revisionHistoryLimit` field to limit the number of CertificateRequests to be kept for a Certificate. + Minimum value is 1. If unset all CertificateRequests will be kept. + +- `cert-manager.io/private-key-algorithm`: (optional) this annotation allows you to + configure `spec.privateKey.algorithm` field to set the algorithm for private key generation for a Certificate. + Valid values are `RSA`, `ECDSA` and `Ed25519`. If unset an algorithm `RSA` will be used. + +- `cert-manager.io/private-key-encoding`: (optional) this annotation allows you to + configure `spec.privateKey.encoding` field to set the encoding for private key generation for a Certificate. + Valid values are `PKCS1` and `PKCS8`. If unset an algorithm `PKCS1` will be used. + +- `cert-manager.io/private-key-size`: (optional) this annotation allows you to + configure `spec.privateKey.size` field to set the size of the private key for a Certificate. + If algorithm is set to `RSA`, valid values are `2048`, `4096` or `8192`, and will default to `2048` if not specified. + If algorithm is set to `ECDSA`, valid values are `256`, `384` or `521`, and will default to `256` if not specified. + If algorithm is set to `Ed25519`, size is ignored. + +- `cert-manager.io/private-key-rotation-policy`: (optional) this annotation allows you to + configure `spec.privateKey.rotationPolicy` field to set the rotation policy of the private key for a Certificate. + Valid values are `Never` and `Always`. If unset a rotation policy `Never` will be used. diff --git a/content/v1.12-docs/usage/ingress.md b/content/v1.12-docs/usage/ingress.md new file mode 100644 index 0000000000..e70c77390f --- /dev/null +++ b/content/v1.12-docs/usage/ingress.md @@ -0,0 +1,175 @@ +--- +title: Securing Ingress Resources +description: 'cert-manager usage: Kubernetes Ingress' +--- + +A common use-case for cert-manager is requesting TLS signed certificates to +secure your ingress resources. This can be done by simply adding annotations to +your `Ingress` resources and cert-manager will facilitate creating the +`Certificate` resource for you. A small sub-component of cert-manager, +ingress-shim, is responsible for this. + +## How It Works + +The sub-component ingress-shim watches `Ingress` resources across your cluster. +If it observes an `Ingress` with annotations described in the [Supported +Annotations](#supported-annotations) section, it will ensure a `Certificate` +resource with the name provided in the `tls.secretName` field and configured as +described on the `Ingress` exists in the `Ingress`'s namespace. For example: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + # add an annotation indicating the issuer to use. + cert-manager.io/cluster-issuer: nameOfClusterIssuer + name: myIngress + namespace: myIngress +spec: + rules: + - host: example.com + http: + paths: + - pathType: Prefix + path: / + backend: + service: + name: myservice + port: + number: 80 + tls: # < placing a host in the TLS config will determine what ends up in the cert's subjectAltNames + - hosts: + - example.com + secretName: myingress-cert # < cert-manager will store the created certificate in this secret. +``` + +## Supported Annotations + +You can specify the following annotations on Ingress resources in order to +trigger Certificate resources to be automatically created: + +- `cert-manager.io/issuer`: the name of the issuer that should issue the certificate + required for this Ingress. + + > ⚠️ This annotation does _not_ assume a namespace scoped issuer. It will + default to cert-manager.io Issuer, however in case of external issuer types, + this should be used for both namespaced and cluster scoped issuer types. + + > ⚠️ If a namespace scoped issuer is used then the issuer *must* be in + the same namespace as the Ingress resource. + +- `cert-manager.io/cluster-issuer`: the name of a cert-manager.io ClusterIssuer + to acquire the certificate required for this Ingress. It does not matter which + namespace your Ingress resides, as ClusterIssuers are non-namespaced + resources. + + > ⚠️ This annotation is a shortcut to refer to to + cert-manager.io ClusterIssuer without having to specify group and kind. It is + _not_ intended to be used to specify an external cluster-scoped issuer- please + use `cert-manager.io/issuer` annotation for those. + +- `cert-manager.io/issuer-kind`: the kind of the external issuer resource, for + example `AWSPCAIssuer`. This is only necessary for out-of-tree issuers. + +- `cert-manager.io/issuer-group`: the API group of the external issuer + controller, for example `awspca.cert-manager.io`. This is only necessary for + out-of-tree issuers. + +- `kubernetes.io/tls-acme: "true"`: this annotation requires additional + configuration of the ingress-shim [see below](#optional-configuration). + Namely, a default Issuer must be specified as arguments to the ingress-shim + container. + +- `acme.cert-manager.io/http01-ingress-class`: this annotation allows you to + configure the ingress class that will be used to solve challenges for this + ingress. Customizing this is useful when you are trying to secure internal + services, and need to solve challenges using a different ingress class to that + of the ingress. If not specified and the `acme-http01-edit-in-place` annotation + is not set, this defaults to the ingress class defined in the Issuer resource. + +- `acme.cert-manager.io/http01-edit-in-place: "true"`: this controls whether the + ingress is modified 'in-place', or a new one is created specifically for the + HTTP01 challenge. If present, and set to "true", the existing ingress will be + modified. Any other value, or the absence of the annotation assumes "false". + This annotation will also add the annotation + `"cert-manager.io/issue-temporary-certificate": "true"` onto created + certificates which will cause a [temporary + certificate](./certificate.md#temporary-certificates-whilst-issuing) to be set + on the resulting Secret until the final signed certificate has been returned. + This is useful for keeping compatibility with the `ingress-gce` component. + +- `cert-manager.io/common-name`: (optional) this annotation allows you to + configure `spec.commonName` for the Certificate to be generated. + +- ` cert-manager.io/duration`: (optional) this annotation allows you to + configure `spec.duration` field for the Certificate to be generated. + +- `cert-manager.io/renew-before`: (optional) this annotation allows you to + configure `spec.renewBefore` field for the Certificate to be generated. + +- `cert-manager.io/usages`: (optional) this annotation allows you to configure + `spec.usages` field for the Certificate to be generated. Pass a string with + comma-separated values i.e "key agreement,digital signature, server auth" + +- `cert-manager.io/revision-history-limit`: (optional) this annotation allows you to + configure `spec.revisionHistoryLimit` field to limit the number of CertificateRequests to be kept for a Certificate. + Minimum value is 1. If unset all CertificateRequests will be kept. + +- `cert-manager.io/private-key-algorithm`: (optional) this annotation allows you to + configure `spec.privateKey.algorithm` field to set the algorithm for private key generation for a Certificate. + Valid values are `RSA`, `ECDSA` and `Ed25519`. If unset an algorithm `RSA` will be used. + +- `cert-manager.io/private-key-encoding`: (optional) this annotation allows you to + configure `spec.privateKey.encoding` field to set the encoding for private key generation for a Certificate. + Valid values are `PKCS1` and `PKCS8`. If unset an algorithm `PKCS1` will be used. + +- `cert-manager.io/private-key-size`: (optional) this annotation allows you to + configure `spec.privateKey.size` field to set the size of the private key for a Certificate. + If algorithm is set to `RSA`, valid values are `2048`, `4096` or `8192`, and will default to `2048` if not specified. + If algorithm is set to `ECDSA`, valid values are `256`, `384` or `521`, and will default to `256` if not specified. + If algorithm is set to `Ed25519`, size is ignored. + +- `cert-manager.io/private-key-rotation-policy`: (optional) this annotation allows you to + configure `spec.privateKey.rotationPolicy` field to set the rotation policy of the private key for a Certificate. + Valid values are `Never` and `Always`. If unset a rotation policy `Never` will be used. + + +## Optional Configuration + +The ingress-shim sub-component is deployed automatically as part of +installation. + +If you would like to use the old +[kube-lego](https://github.com/jetstack/kube-lego) `kubernetes.io/tls-acme: +"true"` annotation for fully automated TLS, you will need to configure a default +`Issuer` when deploying cert-manager. This can be done by adding the following +`--set` when deploying using Helm: + +```bash + --set ingressShim.defaultIssuerName=letsencrypt-prod \ + --set ingressShim.defaultIssuerKind=ClusterIssuer \ + --set ingressShim.defaultIssuerGroup=cert-manager.io +``` + +Or by adding the following arguments to the cert-manager deployment +`podTemplate` container arguments. + +``` + - --default-issuer-name=letsencrypt-prod + - --default-issuer-kind=ClusterIssuer + - --default-issuer-group=cert-manager.io +``` + +In the above example, cert-manager will create `Certificate` resources that +reference the `ClusterIssuer` `letsencrypt-prod` for all Ingresses that have a +`kubernetes.io/tls-acme: "true"` annotation. + +Issuers configured via annotations have a preference over the default issuer. If a default issuer is configured via CLI flags and a `cert-manager.io/cluster-issuer` or `cert-manager.io/issuer` annotation also has been added to an Ingress, the created `Certificate` will refer to the issuer configured via annotation. + +For more information on deploying cert-manager, read the [installation +guide](../installation/README.md). + +## Troubleshooting + +If you do not see a `Certificate` resource being created after applying the ingress-shim annotations check that at least `cert-manager.io/issuer` or `cert-manager.io/cluster-issuer` is set. If you want to use `kubernetes.io/tls-acme: "true"` make sure to have checked all steps above and you might want to look for errors in the cert-manager pod logs if not resolved. \ No newline at end of file diff --git a/content/v1.12-docs/usage/istio.md b/content/v1.12-docs/usage/istio.md new file mode 100644 index 0000000000..86149ae22b --- /dev/null +++ b/content/v1.12-docs/usage/istio.md @@ -0,0 +1,17 @@ +--- +title: Securing Istio Service Mesh +description: 'cert-manager usage: Istio and istio-csr' +--- + +cert-manager can be integrated with [Istio](https://istio.io) using the project +[istio-csr](https://github.com/cert-manager/istio-csr). istio-csr will deploy an +agent that is responsible for receiving certificate signing requests for all +members of the Istio mesh, and signing them through cert-manager. + +[istio-csr](https://github.com/cert-manager/istio-csr) will sign all control +plane and workload certificates via your chosen cert-manager Issuer. + +--- + +Please follow the instructions for installing and using istio-csr on the +[project page](../projects/istio-csr.md). diff --git a/content/v1.12-docs/usage/kube-csr.md b/content/v1.12-docs/usage/kube-csr.md new file mode 100644 index 0000000000..f6abb93f74 --- /dev/null +++ b/content/v1.12-docs/usage/kube-csr.md @@ -0,0 +1,166 @@ +--- +title: Kubernetes CertificateSigningRequests +description: 'cert-manager usage: Kubernetes CertificateSigningRequest resources' +--- + +Kubernetes has an in-built +[CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) +resource. This resource is similar to the cert-manager +[CertificateRequest](../concepts/certificaterequest.md) in that it is used to +request an X.509 signed certificate from a referenced Certificate Authority +(CA). + +Using this resource may be useful for users who are using an application that +supports this resource, but not the cert-manager CertificateRequest resource, +and they still wish for certificates to be signed through cert-manager. + +CertificateSigningRequests reference a `SignerName` or signer as the entity it +wishes to sign its request from. For cert-manager, a signer can be mapped to +either an [Issuer or ClusterIssuer](../configuration/README.md). + +#### Feature State + +This feature is currently in an _experimental_ state, and its behavior is +subject to change in further releases. + +
      + +⛔️ This feature is only enabled by adding it to the `--feature-gates` flag on +the cert-manager controller: + +```bash +--feature-gates=ExperimentalCertificateSigningRequestControllers=true +``` + +Which can be added using Helm: + +```bash +$ helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --set featureGates="ExperimentalCertificateSigningRequestControllers=true" \ + # --set installCRDs=true +``` + +> Note: cert-manager supports signing CertificateSigningRequests +> using all [internal Issuers](../configuration/README.md). + +> Note: cert-manager _does not_ automatically approve CertificateSigningRequests +> that reference a cert-manager [Issuer](../configuration/README.md). Please refer to +> the [Kubernetes documentation](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#request-signing-process) +> for the request process of CertificateSigningRequests. + + +
      + + +## Signer Name + +CertificateSigningRequests contain a +[`spec.signerName`](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#request-signing-process) +field to reference a CA to sign the request. cert-manager Issuers or +ClusterIssuers are referenced in the following form: + +``` +.cert-manager.io/. +``` + +For example, a namespaced Issuer in the namespace `sandbox` with the name +`my-issuer` would be referenced via: + +```yaml + signerName: issuers.cert-manager.io/sandbox.my-issuer +``` + +A ClusterIssuer with the name `my-cluster-issuer` would be referenced via: + +```yaml + signerName: clusterissuers.cert-manager.io/my-cluster-issuer +``` + +### Referencing Namespaced Issuers + +Unlike CertificateRequests, CertificateSigningRequests are cluster scoped +resources. To prevent users from requesting certificates from a namespaced +Issuer in a namespace that they otherwise would not have access to, cert-manager +performs a +[SubjectAccessReview](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#checking-api-access). +This review ensures that the requesting user has the permission to `reference` +the `signers` resource in the given namespace. The name should be either the +name of the Issuer, or `"*"` to reference all Issuers in that namespace. + +An example Role to give permissions to reference Issuers in the `sandbox` +namespace would look like the following: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cert-manager-referencer:my-issuer + namespace: sandbox +rules: +- apiGroups: ["cert-manager.io"] + resources: ["signers"] + verbs: ["reference"] + resourceNames: + - "my-issuer" # To give permission to _only_ reference Issuers with the name 'my-issuer' + - "*" # To give permission to reference Issuers with any name in this namespace +``` + +## Annotations + +To keep feature parity with CertificateRequests, annotations are used to store +values that do not exist as `spec` or `status` fields on the +CertificateSigningRequest resource. These fields are either set by the +_requester_ or by the _signer_ as labelled below. + +Requester annotations: + +- `experimental.cert-manager.io/request-duration`: **Set by the requester**. Accepts + a [Go time duration](https://golang.org/pkg/time/#ParseDuration) string + specifying the requested certificate duration. Defaults to 90 days. Some + signers such as Venafi or ACME typically _do not_ allow requesting a + duration. + +- `experimental.cert-manager.io/request-is-ca`: **Set by the requester**. If set to + `"true"`, will request for a CA certificate. + +- `experimental.cert-manager.io/private-key-secret-name`: **Set by the + requester**. Required only for the SelfSigned signer. Used to reference a + Secret which contains the PEM encoded private key of the requester's X.509 + certificate signing request at key `tls.key`. Used to sign the requester's + request. + +- `venafi.experimental.cert-manager.io/custom-fields`: **Set by the + requester**. Optional for only the Venafi signer. Used for adding custom + fields to the Venafi request. This will only work with Venafi TPP `v19.3` + and higher. The value is a JSON array with objects containing the name and + value keys, for example: + ``` + venafi.experimental.cert-manager.io/custom-fields: |- + [ + {"name": "field-name", "value": "field value"}, + {"name": "field-name-2", "value": "field value 2"} + ] + ``` + +Signer annotations: + +- `venafi.experimental.cert-manager.io/pickup-id`: **Set by the signer**. Only + used for the Venafi signer. Used to record the Venafi Pickup ID of a + certificate signing request that has been submitted to the Venafi API for + collection during issuance. + +## Usage + +CertificateSigningRequests can be manually created using +[cmctl](../reference/cmctl.md#experimental). +This command takes a manifest file containing a +[Certificate](../usage/certificate.md) resource as input. This generates a +private key and creates a CertificateSigningRequest. CertificateSigningRequests +are not approved by default, so you will likely need to approve it manually: + +```bash +$ kubectl certificate approve +``` diff --git a/content/v1.12-docs/usage/prometheus-metrics.md b/content/v1.12-docs/usage/prometheus-metrics.md new file mode 100644 index 0000000000..968ce2077d --- /dev/null +++ b/content/v1.12-docs/usage/prometheus-metrics.md @@ -0,0 +1,69 @@ +--- +title: Prometheus Metrics +description: 'cert-manager usage: Prometheus metrics' +--- + +To help with operations and insights into cert-manager activities, cert-manager exposes metrics in the [Prometheus](https://prometheus.io/) format from the controller component. These are available at the standard `/metrics` path of the controller component's configured HTTP port. + +## Scraping Metrics + +How metrics are scraped will depend how you're operating your Prometheus server(s). These examples presume the [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator) is being used to run Prometheus, and configure Pod or Service Monitor CRDs. + +### Helm + +If you're deploying cert-manager with helm, a `ServiceMonitor` resource can be configured. This configuration should enable metric scraping, and the configuration can be further tweaked as described in the [Helm configuration documentation](https://github.com/cert-manager/cert-manager/blob/master/deploy/charts/cert-manager/README.template.md#configuration). + +```yaml +prometheus: + enabled: true + servicemonitor: + enabled: true +``` + +### Regular Manifests + +If you're not using helm to deploy cert-manager and instead using the provided regular YAML manifests, this example `PodMonitor` and deployment patch should be all you need to start ingesting cert-manager metrics. + +1. [Apply the following patch](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/#use-a-strategic-merge-patch-to-update-a-deployment) to your cert-manager deployment + +```yaml +spec: + template: + spec: + containers: + - name: cert-manager-controller + ports: + - containerPort: 9402 + name: http + protocol: TCP +``` + +2. Create the following `PodMonitor` + +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: cert-manager + namespace: cert-manager + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" +spec: + jobLabel: app.kubernetes.io/name + selector: + matchLabels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + podMetricsEndpoints: + - port: http + honorLabels: true +``` + +## Monitoring Mixin + +Monitoring mixins are a way to bundle common alerts, rules, and dashboards for an application in a configurable and extensible way, using the Jsonnet data templating language. A cert-manager monitoring mixin can be found here https://gitlab.com/uneeq-oss/cert-manager-mixin. Documentation on usage can be found with the `cert-manager-mixin` project. From afa13bfe84fbb55b6f76f1594050e33f1a407112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 19 May 2023 17:59:52 +0200 Subject: [PATCH 052/264] post-release 1.12: bump 1.11 -> 1.12 in gen script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- scripts/gendocs/generate-new-import-path-docs | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/gendocs/generate-new-import-path-docs b/scripts/gendocs/generate-new-import-path-docs index fc5a97361b..09620e5dc4 100755 --- a/scripts/gendocs/generate-new-import-path-docs +++ b/scripts/gendocs/generate-new-import-path-docs @@ -153,6 +153,7 @@ LATEST_VERSION="v1.12-docs" #genversionwithcli "release-1.8" "v1.8-docs" #genversionwithcli "release-1.9" "v1.9-docs" #genversionwithcli "release-1.10" "v1.10-docs" +#genversionwithcli "release-1.11" "v1.11-docs" genversionwithcli "release-1.12" "$LATEST_VERSION" From 9c565534beec93eaff9df95e9064236ad53a8a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 19 May 2023 18:23:46 +0200 Subject: [PATCH 053/264] post-release 1.12: run ./scripts/gendocs/generate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/reference/api-docs.md | 22 ++++- content/v1.12-docs/reference/api-docs.md | 106 +++++++++++++++++++++-- 2 files changed, 116 insertions(+), 12 deletions(-) diff --git a/content/docs/reference/api-docs.md b/content/docs/reference/api-docs.md index 4943e086a6..f098ef7b0b 100644 --- a/content/docs/reference/api-docs.md +++ b/content/docs/reference/api-docs.md @@ -815,7 +815,7 @@ description: >- (Optional) -

      The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources.

      +

      The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. Only one of class, name or ingressClassName may be specified.

      @@ -1893,6 +1893,17 @@ description: >-

      LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer

      + + + lastPrivateKeyHash +
      + string + + + (Optional) +

      LastPrivateKeyHash is a hash of the private key associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer

      + +

      AzureDNSEnvironment (string alias)

      @@ -4151,7 +4162,7 @@ description: >- (Optional) -

      LastFailureTime is the time as recorded by the Certificate controller of the most recent failure to complete a CertificateRequest for this Certificate resource. If set, cert-manager will not re-request another Certificate until 1 hour has elapsed from this time.

      +

      LastFailureTime is set only if the lastest issuance for this Certificate failed and contains the time of the failure. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1). If the latest issuance has succeeded this field will be unset.

      @@ -4518,7 +4529,10 @@ description: >- bool -

      Create enables JKS keystore creation for the Certificate. If true, a file named keystore.jks will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. A file named truststore.jks will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef containing the issuing Certificate Authority

      +

      + Create enables JKS keystore creation for the Certificate. If true, a file named keystore.jks will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. If the issuer provided a CA certificate, a file named truststore.jks will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef + containing the issuing Certificate Authority +

      @@ -4716,7 +4730,7 @@ description: >- bool -

      Create enables PKCS12 keystore creation for the Certificate. If true, a file named keystore.p12 will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. A file named truststore.p12 will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef containing the issuing Certificate Authority

      +

      Create enables PKCS12 keystore creation for the Certificate. If true, a file named keystore.p12 will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. If the issuer provided a CA certificate, a file named truststore.p12 will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef containing the issuing Certificate Authority

      diff --git a/content/v1.12-docs/reference/api-docs.md b/content/v1.12-docs/reference/api-docs.md index 35d0f49d6c..83285c0741 100644 --- a/content/v1.12-docs/reference/api-docs.md +++ b/content/v1.12-docs/reference/api-docs.md @@ -785,6 +785,17 @@ description: >-

      Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort.

      + + + ingressClassName +
      + string + + + (Optional) +

      This field configures the field ingressClassName on the created Ingress resources used to solve ACME challenges that use this challenge solver. This is the recommended way of configuring the ingress class. Only one of class, name or ingressClassName may be specified.

      + + class @@ -793,7 +804,7 @@ description: >- (Optional) -

      The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of ‘class’ or ‘name’ may be specified.

      +

      This field configures the annotation kubernetes.io/ingress.class when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of class, name or ingressClassName may be specified.

      @@ -804,7 +815,7 @@ description: >- (Optional) -

      The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources.

      +

      The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. Only one of class, name or ingressClassName may be specified.

      @@ -975,6 +986,19 @@ description: >-

      If specified, the pod’s service account

      + + + imagePullSecrets +
      + + []Kubernetes core/v1.LocalObjectReference + + + + (Optional) +

      If specified, the pod’s imagePullSecrets

      + +

      ACMEChallengeSolverHTTP01IngressPodTemplate

      @@ -1011,7 +1035,7 @@ description: >- (Optional) -

      PodSpec defines overrides for the HTTP01 challenge solver pod. Only the ‘priorityClassName’, ‘nodeSelector’, ‘affinity’, ‘serviceAccountName’ and ‘tolerations’ fields are supported currently. All other fields will be ignored.

      +

      PodSpec defines overrides for the HTTP01 challenge solver pod. Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. All other fields will be ignored.



      @@ -1074,6 +1098,19 @@ description: >-

      If specified, the pod’s service account

      + + + +
      + imagePullSecrets +
      + + []Kubernetes core/v1.LocalObjectReference + +
      + (Optional) +

      If specified, the pod’s imagePullSecrets

      +
      @@ -1856,6 +1893,17 @@ description: >-

      LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer

      + + + lastPrivateKeyHash +
      + string + + + (Optional) +

      LastPrivateKeyHash is a hash of the private key associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer

      + +

      AzureDNSEnvironment (string alias)

      @@ -4114,7 +4162,7 @@ description: >- (Optional) -

      LastFailureTime is the time as recorded by the Certificate controller of the most recent failure to complete a CertificateRequest for this Certificate resource. If set, cert-manager will not re-request another Certificate until 1 hour has elapsed from this time.

      +

      LastFailureTime is set only if the lastest issuance for this Certificate failed and contains the time of the failure. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1). If the latest issuance has succeeded this field will be unset.

      @@ -4481,7 +4529,10 @@ description: >- bool -

      Create enables JKS keystore creation for the Certificate. If true, a file named keystore.jks will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. A file named truststore.jks will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef containing the issuing Certificate Authority

      +

      + Create enables JKS keystore creation for the Certificate. If true, a file named keystore.jks will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. If the issuer provided a CA certificate, a file named truststore.jks will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef + containing the issuing Certificate Authority +

      @@ -4679,7 +4730,7 @@ description: >- bool -

      Create enables PKCS12 keystore creation for the Certificate. If true, a file named keystore.p12 will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. A file named truststore.p12 will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef containing the issuing Certificate Authority

      +

      Create enables PKCS12 keystore creation for the Certificate. If true, a file named keystore.p12 will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. If the issuer provided a CA certificate, a file named truststore.p12 will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef containing the issuing Certificate Authority

      @@ -4796,6 +4847,31 @@ description: >- +

      ServiceAccountRef

      +

      (Appears on: VaultKubernetesAuth)

      +
      +

      ServiceAccountRef is a service account used by cert-manager to request a token. The audience cannot be configured. The audience is generated by cert-manager and takes the form vault://namespace-name/issuer-name for an Issuer and vault://issuer-name for a ClusterIssuer. The expiration of the token is also set by cert-manager to 10 minutes.

      +
      + + + + + + + + + + + + + +
      FieldDescription
      + name +
      + string +
      +

      Name of the ServiceAccount used to request a token.

      +

      VaultAppRole

      (Appears on: VaultAuth)

      @@ -4846,7 +4922,7 @@ description: >-

      VaultAuth

      (Appears on: VaultIssuer)

      -

      Configuration used to authenticate with a Vault server. Only one of tokenSecretRef, appRole or kubernetes may be specified.

      +

      VaultAuth is configuration used to authenticate with a Vault server. The order of precedence is [tokenSecretRef, appRole or kubernetes].

      @@ -5012,9 +5088,23 @@ description: >- + + + +
      + (Optional)

      The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of ‘ambient credentials’ is not supported.

      + serviceAccountRef +
      + + ServiceAccountRef + +
      + (Optional) +

      A reference to a service account that will be used to request a bound token (also known as “projected token”). Compared to using “secretRef”, using this field means that you don’t rely on statically bound tokens. To use this field, you must configure an RBAC rule to let cert-manager request a token.

      +
      role @@ -5670,5 +5760,5 @@ description: >-

      - Generated with gen-crd-api-reference-docs on git commit 7ebb5f515. + Generated with gen-crd-api-reference-docs on git commit 65bf16d.

      From 540875003373e7bb9c6451da5f63c1e41b8ed613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 19 May 2023 18:47:44 +0200 Subject: [PATCH 054/264] post-release 1.12: bump versions in install instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command: find ./content/docs/installation -name '*.md' -not -path '*/upgrad**' -exec sed -i.bak 's/1.11../1.12.0/g' '{}' \; rm -f **/*.bak Signed-off-by: Maël Valais --- content/docs/installation/README.md | 2 +- content/docs/installation/code-signing.md | 2 +- content/docs/installation/featureflags.md | 2 +- content/docs/installation/helm.md | 10 +++++----- content/docs/installation/kubectl.md | 2 +- .../installation/operator-lifecycle-manager.md | 2 +- .../docs/installation/supported-releases.md | 18 +++++++++--------- content/v1.12-docs/installation/README.md | 2 +- .../v1.12-docs/installation/code-signing.md | 2 +- .../v1.12-docs/installation/featureflags.md | 2 +- content/v1.12-docs/installation/helm.md | 10 +++++----- content/v1.12-docs/installation/kubectl.md | 2 +- .../installation/operator-lifecycle-manager.md | 2 +- .../installation/supported-releases.md | 18 +++++++++--------- 14 files changed, 38 insertions(+), 38 deletions(-) diff --git a/content/docs/installation/README.md b/content/docs/installation/README.md index df829ac3d1..e3d09c6c01 100644 --- a/content/docs/installation/README.md +++ b/content/docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/docs/installation/code-signing.md b/content/docs/installation/code-signing.md index cd05c04f2f..4f85faf92e 100644 --- a/content/docs/installation/code-signing.md +++ b/content/docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.11.0 # change as needed +IMAGE_TAG=v1.12.0 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/docs/installation/featureflags.md b/content/docs/installation/featureflags.md index 10548df563..65ce69a027 100644 --- a/content/docs/installation/featureflags.md +++ b/content/docs/installation/featureflags.md @@ -62,7 +62,7 @@ See `--feature-gates` flags on cert-manager controller and webhook to enable any - `StableCertificateRequestName`. Added in cert-manager 1.10.0. Will enable generation of `CertificateRequest` resources with a fixed name. See [`cert-manager#5487`](https://github.com/cert-manager/cert-manager/pull/5487) -- `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.11.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) +- `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.12.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) - `ValidateCAA`. Added in cert-manager 0.7.2. CAA checking when issuing a certificate. diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index f9b1db130c..18a7af9695 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -44,7 +44,7 @@ or using the `installCRDs` option when installing the Helm chart. ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -65,7 +65,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.11.0 \ + --version v1.12.0 \ # --set installCRDs=true ``` @@ -78,7 +78,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.11.0 \ + --version v1.12.0 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -109,7 +109,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.11.0 + version: v1.12.0 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -140,7 +140,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.11.0 \ + --version v1.12.0 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index 742f50768d..655f0ecbdf 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -19,7 +19,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index f9982899ad..52fa7ad06b 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.11.0 \ +kubectl patch csv cert-manager.v1.12.0 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 930152f250..54b75d4c33 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -20,7 +20,7 @@ cert-manager expects that ServerSideApply is enabled in the cluster for all vers | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| | [1.12][] | May 19, 2024 | End of September, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | -| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | +| [1.12.0] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | \*ServerSideApply should be enabled in the cluster @@ -45,19 +45,19 @@ Dates in the future are uncertain and might change. | [1.4][] | Jun 15, 2021 | Oct 26, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | | [1.3][] | Apr 08, 2021 | Aug 11, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | | [1.2][] | Feb 10, 2021 | Jun 15, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | -| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | -| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.12.0 1.21 | 3.11 → 4.7 | +| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | | [0.11][] | Oct 10, 2019 | Jan 21, 2020 | 1.9 → 1.21 | 3.09 → 4.7 | [s]: #kubernetes-supported-versions [1.13]: https://github.com/cert-manager/cert-manager/milestone/34 [1.12]: https://cert-manager.io/docs/release-notes/release-notes-1.12 -[1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 +[1.12.0 https://cert-manager.io/docs/release-notes/release-notes-1.11 [1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 [1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 [1.8]: https://cert-manager.io/docs/release-notes/release-notes-1.8 diff --git a/content/v1.12-docs/installation/README.md b/content/v1.12-docs/installation/README.md index df829ac3d1..e3d09c6c01 100644 --- a/content/v1.12-docs/installation/README.md +++ b/content/v1.12-docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/v1.12-docs/installation/code-signing.md b/content/v1.12-docs/installation/code-signing.md index cd05c04f2f..4f85faf92e 100644 --- a/content/v1.12-docs/installation/code-signing.md +++ b/content/v1.12-docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.11.0 # change as needed +IMAGE_TAG=v1.12.0 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/v1.12-docs/installation/featureflags.md b/content/v1.12-docs/installation/featureflags.md index 10548df563..65ce69a027 100644 --- a/content/v1.12-docs/installation/featureflags.md +++ b/content/v1.12-docs/installation/featureflags.md @@ -62,7 +62,7 @@ See `--feature-gates` flags on cert-manager controller and webhook to enable any - `StableCertificateRequestName`. Added in cert-manager 1.10.0. Will enable generation of `CertificateRequest` resources with a fixed name. See [`cert-manager#5487`](https://github.com/cert-manager/cert-manager/pull/5487) -- `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.11.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) +- `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.12.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) - `ValidateCAA`. Added in cert-manager 0.7.2. CAA checking when issuing a certificate. diff --git a/content/v1.12-docs/installation/helm.md b/content/v1.12-docs/installation/helm.md index f9b1db130c..18a7af9695 100644 --- a/content/v1.12-docs/installation/helm.md +++ b/content/v1.12-docs/installation/helm.md @@ -44,7 +44,7 @@ or using the `installCRDs` option when installing the Helm chart. ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -65,7 +65,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.11.0 \ + --version v1.12.0 \ # --set installCRDs=true ``` @@ -78,7 +78,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.11.0 \ + --version v1.12.0 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -109,7 +109,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.11.0 + version: v1.12.0 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -140,7 +140,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.11.0 \ + --version v1.12.0 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/v1.12-docs/installation/kubectl.md b/content/v1.12-docs/installation/kubectl.md index 742f50768d..655f0ecbdf 100644 --- a/content/v1.12-docs/installation/kubectl.md +++ b/content/v1.12-docs/installation/kubectl.md @@ -19,7 +19,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/v1.12-docs/installation/operator-lifecycle-manager.md b/content/v1.12-docs/installation/operator-lifecycle-manager.md index f9982899ad..52fa7ad06b 100644 --- a/content/v1.12-docs/installation/operator-lifecycle-manager.md +++ b/content/v1.12-docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.11.0 \ +kubectl patch csv cert-manager.v1.12.0 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` diff --git a/content/v1.12-docs/installation/supported-releases.md b/content/v1.12-docs/installation/supported-releases.md index 930152f250..54b75d4c33 100644 --- a/content/v1.12-docs/installation/supported-releases.md +++ b/content/v1.12-docs/installation/supported-releases.md @@ -20,7 +20,7 @@ cert-manager expects that ServerSideApply is enabled in the cluster for all vers | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| | [1.12][] | May 19, 2024 | End of September, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | -| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | +| [1.12.0] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | \*ServerSideApply should be enabled in the cluster @@ -45,19 +45,19 @@ Dates in the future are uncertain and might change. | [1.4][] | Jun 15, 2021 | Oct 26, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | | [1.3][] | Apr 08, 2021 | Aug 11, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | | [1.2][] | Feb 10, 2021 | Jun 15, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | -| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | -| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.12.0 1.21 | 3.11 → 4.7 | +| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | +| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | | [0.11][] | Oct 10, 2019 | Jan 21, 2020 | 1.9 → 1.21 | 3.09 → 4.7 | [s]: #kubernetes-supported-versions [1.13]: https://github.com/cert-manager/cert-manager/milestone/34 [1.12]: https://cert-manager.io/docs/release-notes/release-notes-1.12 -[1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 +[1.12.0 https://cert-manager.io/docs/release-notes/release-notes-1.11 [1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 [1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 [1.8]: https://cert-manager.io/docs/release-notes/release-notes-1.8 From 893b5f827a68559ed1cc5109f9d1bbab7d5ed28f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 19 May 2023 19:08:06 +0200 Subject: [PATCH 055/264] mdspell: api-reference.md doesn't need to be checked in any folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b062a0eb3..55a5892a89 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "check": "concurrently --group --timings npm:check:* # Run all the npm check:* scripts in parallel", "check:next-lint": "next lint", "check:links": "find content/docs -type f -name '*.md' | xargs markdown-link-check --quiet --config markdown-link-check.json 2>&1 | awk -v RS=FILE: '/ERROR/{f=1; print RS $0} END{exit f}' # Split into records based on the word FILE and print only records containing word ERROR", - "check:spelling": "FORCE_COLOR=1 mdspell --report --en-us --ignore-numbers --ignore-acronyms 'content/**/*.md' 'content/**/*.html' '_layouts/*.html' '_includes/*.html' '*.html' '!**/api-docs.md' '!content/docs/projects/approver-policy/api-reference.md' # Force color output in mdspell. # See https://github.com/lukeapage/node-markdown-spellcheck/issues/36#issuecomment-482649408 ", + "check:spelling": "FORCE_COLOR=1 mdspell --report --en-us --ignore-numbers --ignore-acronyms 'content/**/*.md' 'content/**/*.html' '_layouts/*.html' '_includes/*.html' '*.html' '!**/api-docs.md' '!content/*docs/projects/approver-policy/api-reference.md' # Force color output in mdspell. # See https://github.com/lukeapage/node-markdown-spellcheck/issues/36#issuecomment-482649408 ", "check:markdown": "remark --rc-path .remarkrc --frail --quiet content/" }, "lint-staged": { From fb6c781704626c104e4e6a9bcc6017aa46e76fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 22 May 2023 18:11:49 +0200 Subject: [PATCH 056/264] post-release 1.12: fix supported-releases.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- .../docs/installation/supported-releases.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 54b75d4c33..930152f250 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -20,7 +20,7 @@ cert-manager expects that ServerSideApply is enabled in the cluster for all vers | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| | [1.12][] | May 19, 2024 | End of September, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | -| [1.12.0] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | +| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | \*ServerSideApply should be enabled in the cluster @@ -45,19 +45,19 @@ Dates in the future are uncertain and might change. | [1.4][] | Jun 15, 2021 | Oct 26, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | | [1.3][] | Apr 08, 2021 | Aug 11, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | | [1.2][] | Feb 10, 2021 | Jun 15, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | -| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.12.0 1.21 | 3.11 → 4.7 | -| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | +| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | +| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | | [0.11][] | Oct 10, 2019 | Jan 21, 2020 | 1.9 → 1.21 | 3.09 → 4.7 | [s]: #kubernetes-supported-versions [1.13]: https://github.com/cert-manager/cert-manager/milestone/34 [1.12]: https://cert-manager.io/docs/release-notes/release-notes-1.12 -[1.12.0 https://cert-manager.io/docs/release-notes/release-notes-1.11 +[1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 [1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 [1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 [1.8]: https://cert-manager.io/docs/release-notes/release-notes-1.8 From e7bbe602233b4ec93404fc596f93cbd1f273f192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 22 May 2023 17:54:26 +0200 Subject: [PATCH 057/264] supported-releases: 1.11 supports 1.27 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I also removed the mention to service-side apply since 1.10 was the only version that was spanning across Kubernetes version that didn't have server-side apply enabled by default. Co-Authored-By: Irbe Krumina Signed-off-by: Maël Valais --- content/docs/installation/supported-releases.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 930152f250..562c087f84 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -13,16 +13,12 @@ Each release is supported for a period of four months, and we aim to create a ne release roughly every two months, accounting for holiday periods, major conferences and other world events. -cert-manager expects that ServerSideApply is enabled in the cluster for all version of Kubernetes from 1.24 and above. -

      Currently supported releases

      | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| | [1.12][] | May 19, 2024 | End of September, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | -| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | - -\*ServerSideApply should be enabled in the cluster +| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.27 | 4.8 → 4.13 | ## Upcoming releases From eccc7f253b7846939280e362f7f229c366aec99a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 22 May 2023 18:16:24 +0200 Subject: [PATCH 058/264] release-notes: forgot to change manifest.json + release-notes/README.md's index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/installation/supported-releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 562c087f84..0f3ac5c1ea 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -18,7 +18,7 @@ and other world events. | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| | [1.12][] | May 19, 2024 | End of September, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | -| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.27 | 4.8 → 4.13 | +| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.27 | 4.8 → 4.14 | ## Upcoming releases From 422d6af677c4274df49bc77f6bcd072ee2816a42 Mon Sep 17 00:00:00 2001 From: relativelyrehan Date: Wed, 24 May 2023 01:10:38 +0530 Subject: [PATCH 059/264] fix alt tag issue Signed-off-by: relativelyrehan --- components/Feature.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Feature.jsx b/components/Feature.jsx index b1c562e8ac..9e428356f7 100644 --- a/components/Feature.jsx +++ b/components/Feature.jsx @@ -7,7 +7,7 @@ export default function Feature({ feature }) {
      feature icon Date: Tue, 30 May 2023 16:33:13 +0900 Subject: [PATCH 060/264] fix(supported-releases): fix the year of the 1.12 release date and eol Signed-off-by: Rintaro Okamura --- content/docs/installation/supported-releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 0f3ac5c1ea..73f5117bd3 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -17,7 +17,7 @@ and other world events. | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| -| [1.12][] | May 19, 2024 | End of September, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | +| [1.12][] | May 19, 2023 | End of September, 2023 | 1.22 → 1.27 | 4.9 → 4.14 | | [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.27 | 4.8 → 4.14 | ## Upcoming releases From 64c059fa61fc8e6a24f3639438022829f440fb16 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 30 May 2023 13:32:13 +0200 Subject: [PATCH 061/264] Fix supported-releases.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/installation/supported-releases.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 73f5117bd3..0a2b8db0d4 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -22,9 +22,9 @@ and other world events. ## Upcoming releases -| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:----------------------:|:---------------------:|:----------------------------------:|:---------------------------------:| -| [1.13][] | End of September, 2024 | End of November, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:----------------------:|:----------------------:|:----------------------------------:|:---------------------------------:| +| [1.13][] | End of July, 2023 | End of November, 2023 | 1.22 → 1.27 | 4.9 → 4.14 | Dates in the future are uncertain and might change. From 0001ff54d2591148b3274f8d7a05d5e6861c28a1 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 2 Jun 2023 08:54:40 +0200 Subject: [PATCH 062/264] fix mistake in EOL year Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/installation/supported-releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 0a2b8db0d4..160ec17fc4 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -32,7 +32,7 @@ Dates in the future are uncertain and might change. | Release | Release Date | EOL | Compatible Kubernetes versions | Compatible OpenShift versions | |----------|:------------:|:------------:|:------------------------------:|:-----------------------------:| -| [1.10][] | Oct 17, 2022 | May 19, 2024 | 1.20 → 1.26 | 4.7 → 4.13 | +| [1.10][] | Oct 17, 2022 | May 19, 2023 | 1.20 → 1.26 | 4.7 → 4.13 | | [1.9][] | Jul 22, 2022 | Jan 11, 2023 | 1.20 → 1.24 | 4.7 → 4.11 | | [1.8][] | Apr 05, 2022 | Oct 17, 2022 | 1.19 → 1.24 | 4.6 → 4.11 | | [1.7][] | Jan 26, 2021 | Jul 22, 2022 | 1.18 → 1.23 | 4.5 → 4.9 | From c67add7de7a23d2d2d287278a225059699ec592c Mon Sep 17 00:00:00 2001 From: irbekrm Date: Fri, 2 Jun 2023 13:39:38 +0100 Subject: [PATCH 063/264] Explain what FAO stands for Signed-off-by: irbekrm --- .spelling | 1 + content/docs/release-notes/release-notes-1.12.md | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.spelling b/.spelling index 313785d7f0..e8e149e9f5 100644 --- a/.spelling +++ b/.spelling @@ -33,6 +33,7 @@ illrill davidsbond fvlaicu facto +fao 4molybdenum2 jetstack-bot codegen diff --git a/content/docs/release-notes/release-notes-1.12.md b/content/docs/release-notes/release-notes-1.12.md index 9a048ec28e..688010176e 100644 --- a/content/docs/release-notes/release-notes-1.12.md +++ b/content/docs/release-notes/release-notes-1.12.md @@ -40,7 +40,7 @@ memory consumption. Caching of the full contents of all cluster `Secret`s can now be disabled by setting a `SecretsFilteredCaching` alpha feature gate to true. This will ensure that only `Secret` resources that are labelled with -`controller.cert-manager.io/fao` label are cached in full. Cert-manager +`controller.cert-manager.io/fao` label [^1] are cached in full. Cert-manager automatically adds this label to all `Certificate` `Secret`s. This change has been placed behind alpha feature gate as it could potentially @@ -268,3 +268,5 @@ time and resources towards the continued maintenance of cert-manager projects. ### Uncategorized - We have replaced our python boilerplate checker with an installed Go version, removing the need to have Python installed when developing or building cert-manager. ([#6000](https://github.com/cert-manager/cert-manager/pull/6000), [@SgtCoDFish](https://github.com/SgtCoDFish)) + +[^1]: fao = 'for attention of' From 6f43a21f4941406d0c301823495312eaf449e4b8 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 28 Apr 2023 08:29:07 +0100 Subject: [PATCH 064/264] Add a section about best practice and liveness probes Signed-off-by: Richard Wall --- .spelling | 4 + content/docs/installation/best-practice.md | 92 ++++++++++++++++++++-- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/.spelling b/.spelling index e8e149e9f5..77c2c03570 100644 --- a/.spelling +++ b/.spelling @@ -126,6 +126,7 @@ DNSPod DNSimple DaemonSet DataDog +Datree Dean-Coakley DigitalOcean OVHCloud @@ -188,6 +189,7 @@ k8s KubeCon Kubernetes Kyverno +Learnk8s LuCI Maartje MacOS @@ -316,6 +318,7 @@ google-cas-issuer goroutine hardcodes hardcoded +healthz honour hostname https @@ -362,6 +365,7 @@ labelled lalitadithya ldflag lifecycle +liveness loadbalancer longkai loopback diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index f2ca51b603..9ba3655820 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -1,19 +1,102 @@ --- title: Best Practice -description: Learn how to deploy cert-manager to comply with popular security standards such as those produced by the CIS, NSA, and BSI. +description: | + Learn about best practices for deploying cert-manager in production, + and how to configure cert-manager to comply with popular security standards + such as those produced by the CIS, NSA, and BSI. --- -Learn how to deploy cert-manager to comply with popular security standards such as +In this section you will learn how to configure cert-manager to comply with popular security standards such as the [CIS Kubernetes Benchmark](https://www.cisecurity.org/benchmark/kubernetes/), the [NSA Kubernetes Hardening Guide](https://media.defense.gov/2022/Aug/29/2003066362/-1/-1/0/CTR_KUBERNETES_HARDENING_GUIDANCE_1.2_20220829.PDF), or the [BSI Kubernetes Security Recommendations](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Grundschutz/International/bsi_it_gs_comp_2022.pdf?__blob=publicationFile&v=2#page=475). +And you will learn about best practices for deploying cert-manager in production; +such as those enforced by tools like [Datree and its built in rules](https://hub.datree.io/built-in-rules), +and those documented by the likes of [Learnk8s in their "Kubernetes production best practices" checklist](https://learnk8s.io/production-best-practices/). + ## Overview -The default cert-manager resources in the Helm chart or YAML manifests (Deployment, Pod, ServiceAccount etc) are designed for backwards compatibility rather than for best practice or maximum security. +The default cert-manager resources in the Helm chart or YAML manifests (Deployment, Pod, ServiceAccount etc) +are designed for backwards compatibility rather than for best practice or maximum security. You may find that the default resources do not comply with the security policy on your Kubernetes cluster and in that case you can modify the installation configuration using Helm chart values to override the defaults. +## Use Liveness Probes + +An example of this recommendation is found in the Datree Documentation: +[Ensure each container has a configured liveness probe](https://hub.datree.io/built-in-rules/ensure-liveness-probe): +> Liveness probes allow Kubernetes to determine when a pod should be replaced. +> They are fundamental in configuring a resilient cluster architecture. + +The cert-manager webhook and controller Pods do have liveness probes, +but only the webhook liveness probe is enabled by default. +The cainjector Pod does not have a liveness probe, yet. +More information below. + +### webhook + +The [cert-manager webhook](../concepts/webhook.md) has a [liveness probe which is enabled by default](https://github.com/cert-manager/cert-manager/blob/eafe0d0aae4b7a9411825424f6b43fb623e1ba65/deploy/charts/cert-manager/templates/webhook-deployment.yaml#L108C1-L121) +and the [timings and thresholds can be configured using Helm values](https://github.com/cert-manager/cert-manager/blob/eafe0d0aae4b7a9411825424f6b43fb623e1ba65/deploy/charts/cert-manager/README.template.md?plain=1#L181-L185). + +### controller + +> ℹ️ The cert-manager controller liveness probe was introduced in cert-manager `v1.12.0`. + +The cert-manager controller has a liveness probe, but it is **disabled by default**. +You can enable it using the Helm chart value `livenessProbe.enabled=true`, +but first read the background information below. + +The liveness probe for the cert-manager controller is an HTTP probe which connects +to the `/livez` endpoint of a healthz server which listens on port 9443 and runs in its own thread. +The `/livez` endpoint currently reports the combined status of the following sub-systems +and each sub-system has its own `/livez` endpoint. These are: + +* `/livez/leaderElection`: Returns an error if the leader election record has not been renewed + or if the leader election thread has exited without also crashing the parent process. + +> ℹ️ In future more sub-systems could be checked by the `/livez` endpoint, +> similar to how Kubernetes [ensure logging is not blocked](https://github.com/kubernetes/kubernetes/pull/64946) +> and have [health checks for each controller](https://github.com/kubernetes/kubernetes/pull/104667). +> +> 📖 Read about [how to access individual health checks and verbose status information](https://kubernetes.io/docs/reference/using-api/health-checks/) (cert-manager uses the same healthz endpoint multiplexer as Kubernetes). + +### cainjector + +The cainjector Pod does not have a liveness probe or a `/livez` healthz endpoint, +but there is justification for it in the GitHub issue: +[cainjector in a zombie state after attempting to shut down](https://github.com/cert-manager/cert-manager/issues/5889). +Please add your remarks to that issue if you have also experienced this specific problem, +and add your remarks to [Helm: Allow configuration of readiness, liveness and startup probes for all created Pods](https://github.com/cert-manager/cert-manager/issues/5626) if you have a general request for a liveness probe in cainjector. + +### Background Information + +The cert-manager `controller` process and the `cainjector` process, +both use the Kubernetes [leader election library](https://pkg.go.dev/k8s.io/client-go/tools/leaderelection), +to ensure that only one replica of each process can be active at any one time. +The Kubernetes control-plane components also use this library. + +The leader election code runs in a loop in a separate thread (go routine). +If it initially wins the leader election race and if it later fails to renew its leader election lease, it exits. +If the leader election thread exits, all the other threads are gracefully shutdown and then the process exits. +Similarly, if any of the other main threads exit unexpectedly, +that will trigger the orderly shutdown of the remaining threads and the process will exit. + +This adheres to the principle that [Containers should crash when there's a fatal error](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-revisited-how-to-avoid-shooting-yourself-in-the-other-foot/#letitcrash). +Kubernetes will restart the crashed container, and if it crashes repeatedly, +there will be increasing time delays between successive restarts. + +For this reason, the liveness probe should only be needed if there is a bug in this orderly shutdown process, +or if there is a bug in one of the other threads which causes the process to deadlock and not shutdown. + +You may want to enable the liveness probe anyway, for defense against unforeseen bugs and deadlocks, +but you will need to monitor the processes closely and, +tweak the [various liveness probe time settings and thresholds](https://github.com/cert-manager/cert-manager/blob/eafe0d0aae4b7a9411825424f6b43fb623e1ba65/deploy/charts/cert-manager/values.yaml#L254-L268), if necessary. + +> 📖 Read [Configure Liveness, Readiness and Startup Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#before-you-begin) in the Kubernetes documentation, paying particular attention to the notes and cautions in that document. +> +> 📖 Read [Shooting Yourself in the Foot with Liveness Probes](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-how-to-avoid-shooting-yourself-in-the-foot/#shootingyourselfinthefootwithlivenessprobes) for more cautionary information about liveness probes. + ## Restrict Auto-Mount of Service Account Tokens This recommendation is described in the [Kyverno Policy Catalogue](https://kyverno.io/policies/other/restrict_automount_sa_token/restrict_automount_sa_token/) as follows: @@ -25,7 +108,7 @@ This recommendation is described in the [Kyverno Policy Catalogue](https://kyver The cert-manager components *do* need to speak to the API server but we still recommend setting `automountServiceAccountToken: false` for the following reasons: 1. Setting `automountServiceAccountToken: false` will allow cert-manager to be installed on clusters where Kyverno (or some other policy system) is configured to deny Pods that have this field set to `true`. The Kubernetes default value is `true`. -2. With `automountServiceAccountToken: true`, *all* the containers in the Pod will mount the ServiceAccount token, including side-car and init containers that might have been injected into the cert-manager Pod resources by Kubernetes admission controllers. +2. With `automountServiceAccountToken: true`, *all* the containers in the Pod will mount the ServiceAccount token, including side-car and init containers that might have been injected into the cert-manager Pod resources by Kubernetes admission controllers. The principle of least privilege suggests that it is better to explicitly mount the ServiceAccount token into the cert-manager containers. So it is recommended to set `automountServiceAccountToken: false` and manually add a projected `Volume` to each of the cert-manager Deployment resources, containing the ServiceAccount token, CA certificate and namespace files that would normally be [added automatically by the Kubernetes ServiceAccount controller](https://github.com/kubernetes/kubernetes/blob/3992eda8e61725c470fb6141a7fe4e7f9ee31ea5/plugin/pkg/admission/serviceaccount/admission.go#L421-L460), @@ -45,4 +128,3 @@ Download the following Helm chart values file and supply it to `helm install`, ` This list of recommendations is a work-in-progress. If you have other best practice recommendations please [contribute to this page](../contributing/contributing-flow.md). - From 300e47bea023455666e3224ae640492bea956c6e Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 28 Apr 2023 08:48:55 +0100 Subject: [PATCH 065/264] Add a release note Signed-off-by: Richard Wall --- content/docs/release-notes/release-notes-1.12.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/content/docs/release-notes/release-notes-1.12.md b/content/docs/release-notes/release-notes-1.12.md index 688010176e..4194faa683 100644 --- a/content/docs/release-notes/release-notes-1.12.md +++ b/content/docs/release-notes/release-notes-1.12.md @@ -143,6 +143,17 @@ and ClusterIssuers. > 📖 Read more about `ingressClassName` in the documentation page [HTTP01](../configuration/acme/http01/#ingressclassname). +### Liveness probe and healthz endpoint in the controller + +A healthz HTTP server has been added to the controller component. +It serves a `/livez` endpoint, which reports the health status of the leader election system. +If the leader process has failed to renew its lease but has unexpectedly failed to exit, +the `/livez` endpoint will return an error code and an error message. +In conjunction with a new liveness probe in the controller Pod, +this will cause the controller to be restarted by the kubelet. + +> 📖 Read more about this new feature in [Best Practice: Use Liveness Probes](../installation/best-practice.md#use-liveness-probes). + ## Community We extend our gratitude to all the open-source contributors who have made From 7592cc1f74fbb6686bab9e92d72876abcba4cb11 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 28 Apr 2023 12:42:49 +0100 Subject: [PATCH 066/264] Invite users to give feedback about the controller liveness probe And whether it should be turned on by default. Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 9ba3655820..1fd81277ae 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -41,12 +41,20 @@ and the [timings and thresholds can be configured using Helm values](https://git ### controller -> ℹ️ The cert-manager controller liveness probe was introduced in cert-manager `v1.12.0`. +> ℹ️ The cert-manager controller liveness probe was introduced in cert-manager release `1.12`. The cert-manager controller has a liveness probe, but it is **disabled by default**. You can enable it using the Helm chart value `livenessProbe.enabled=true`, but first read the background information below. +> 📢 The controller liveness probe is a new feature in cert-manager release 1.12 +> and it is disabled by default, as a precaution, in case it causes problems in the field. +> [Please get in touch](../contributing/README.md) +> and tell is if you have enabled the controller liveness probe in production +> and tell us whether you would like it to be turned on by default. +> Tell us about any circumstances where the controller has become stuck +> and where the liveness probe has been necessary to automatically restart the process. + The liveness probe for the cert-manager controller is an HTTP probe which connects to the `/livez` endpoint of a healthz server which listens on port 9443 and runs in its own thread. The `/livez` endpoint currently reports the combined status of the following sub-systems From 498a79ce22c13bc5daa648e68b398d40a579e6d3 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 18 May 2022 20:22:44 +0100 Subject: [PATCH 067/264] Imagining the characteristics of some of the audiences Signed-off-by: Richard Wall --- .../2022/improve-navigation-and-structure.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/content/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md b/content/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md index 8f23636af3..305d14e7fe 100644 --- a/content/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md +++ b/content/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md @@ -165,6 +165,60 @@ We would like them to quickly and easily find the information they need. By making it easier for each group to find the information they need we aim to reduce the number of support queries. +## Audiences + +### New User + +Has never used cert-manager and may never have used Kubernetes. +Wants to find out what cert-manager can offer. +May have heard about cert-manager in another tutorial. +May want to know what are the alternatives to cert-manager and the tradeoffs. +Needs to install cert-manager quickly so that they evaluate it on their laptop. +Needs to learn basic configuration of cert-manager. +Needs to understand what are the next steps. + +### Ongoing User + +A programmer who wants to deploy a TLS protected APP. +Knows that cert-manager has been installed by their cluster administrator. +Has an existing Issuer or ClusterIssuer. +Needs to know how to create a Certificate which is appropriate for their application. E.g. +* Create a certificate for their PostgreSQL database +* Create an certificate for their Ingress / Gateway +Needs to know how to debug why their certificate hasn’t renewed +Needs to understand the error messages on cert-manager Certificates and Certificate requests +Needs to know which errors they can fix and which errors require assistance from their cluster administrator. + +### Cluster Administrator + +Knows Kubernetes. +Has a long running cert-manager installation. +Wants to know how to configure it and upgrade it for optimum performance. +Wants to optimise for large numbers of certificates. +Wants to upgrade from older versions. +Wants to monitor cert-manager performance +Wants to set up alerts to notify them when cert-manager goes wrong. +May want to configure cert-manager for multiple cloud providers. +May want to get cert-manager working with some other cluster scoped system like Istio or knative. + +### Integrator + +May want to allow cert-manager users to make use of a custom Certificate service. +May want to integrate cert-manager with a DNS API for ACME DNS01. +May want to depend on cert-manager for managing TLS certificates for a higher level system. +Needs to learn how to write plugins / extensions for cert-manager. +Needs links to state-of-the-art examples of plugins and extensions. + +### New Contributor + +Wants to report a bug in cert-manager. +Wants to fix a bug in cert-manager. +Wants to suggest a feature for cert-manager. +Wants to implement a feature for cert-manager. +Needs to learn how to navigate the cert-manager code. +Learn cert-manager coding standards and house style. +Needs to know how to run the tests for cert-manager. + ## Scope The scope of this project is as follows: From e2d380dfa6da2395322701d7bd19c3ef589fc6e5 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 9 Jun 2023 11:33:27 +0100 Subject: [PATCH 068/264] Fix spelling errors Signed-off-by: Richard Wall --- .spelling | 1 + .../2022/improve-navigation-and-structure.md | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.spelling b/.spelling index 77c2c03570..3580cb7ec8 100644 --- a/.spelling +++ b/.spelling @@ -211,6 +211,7 @@ PEM PKCS#12 PKCS#8 Pomerium +PostgreSQL PowerShell Prometheus P-521 diff --git a/content/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md b/content/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md index 305d14e7fe..496ef1be33 100644 --- a/content/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md +++ b/content/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md @@ -172,7 +172,7 @@ By making it easier for each group to find the information they need we aim to r Has never used cert-manager and may never have used Kubernetes. Wants to find out what cert-manager can offer. May have heard about cert-manager in another tutorial. -May want to know what are the alternatives to cert-manager and the tradeoffs. +May want to know what are the alternatives to cert-manager and the trade offs. Needs to install cert-manager quickly so that they evaluate it on their laptop. Needs to learn basic configuration of cert-manager. Needs to understand what are the next steps. @@ -194,12 +194,12 @@ Needs to know which errors they can fix and which errors require assistance from Knows Kubernetes. Has a long running cert-manager installation. Wants to know how to configure it and upgrade it for optimum performance. -Wants to optimise for large numbers of certificates. +Wants to optimize for large numbers of certificates. Wants to upgrade from older versions. Wants to monitor cert-manager performance Wants to set up alerts to notify them when cert-manager goes wrong. May want to configure cert-manager for multiple cloud providers. -May want to get cert-manager working with some other cluster scoped system like Istio or knative. +May want to get cert-manager working with some other cluster scoped system like Istio or Knative. ### Integrator From 6fcefa13e64c8f326a848a721bb2d0d15514a098 Mon Sep 17 00:00:00 2001 From: Mohamed Shahat <8035442+mshahat@users.noreply.github.com> Date: Wed, 14 Jun 2023 16:07:42 +0100 Subject: [PATCH 069/264] Fix docs broken link to Venafi OAuth config Fix docs broken link to Venafi docs Signed-off-by: Mohamed Shahat --- content/docs/configuration/venafi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/configuration/venafi.md b/content/docs/configuration/venafi.md index 93498b0ace..0be6c09e3c 100644 --- a/content/docs/configuration/venafi.md +++ b/content/docs/configuration/venafi.md @@ -139,7 +139,7 @@ credentials. ### Access Token Authentication -1. [Set up token authentication](https://docs.venafi.com/Docs/21.1/TopNav/Content/SDK/AuthSDK/t-SDKa-Setup-OAuth.php). +1. [Set up token authentication](https://docs.venafi.com/Docs/23.1/TopNav/Content/SDK/AuthSDK/t-SDKa-Setup-OAuth.php). NOTE: Do not select "Refresh Token Enabled" and set a *long* "Token Validity (days)". From 2d5b86e71e3d2d83143223a8fb1fa2d27b174d86 Mon Sep 17 00:00:00 2001 From: Elan Hasson <234704+ElanHasson@users.noreply.github.com> Date: Sun, 18 Jun 2023 00:34:49 -0400 Subject: [PATCH 070/264] Correct `kubectl operator install` for latest version of operator-sdk Fixes https://github.com/cert-manager/website/issues/981 Signed-off-by: Elan Hasson <234704+ElanHasson@users.noreply.github.com> --- content/docs/installation/operator-lifecycle-manager.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index 52fa7ad06b..15951779cc 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -41,7 +41,7 @@ from the [Krew Kubectl plugins index][] and then use that to install the cert-ma ```sh operator-sdk olm install kubectl krew install operator -kubectl operator install cert-manager -n operators --channel stable --approval Automatic +kubectl operator install cert-manager -n cert-manager --channel candidate --approval Automatic --create-operator-group ``` You can monitor the progress of the installation as follows: From 807579db7aff793585d1870aa814eecbaa91a550 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Fri, 19 May 2023 15:16:32 +0100 Subject: [PATCH 071/264] post-release 1.12: update the release process with the latest process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I re-arranged the website PR instructions so that the pre-release steps for the website are shown first. I also made it clearer that we produce two types of release notes: the GitHub release description and the "Release Note" page on the website. - Mention "How we determine supported Kubernetes". - Mention how to transform GitHub references into links in the `release-notes.md` file. - Mention bump 1.12 in generate-new-import-path-docs - Mention the freeze of docs/ that gets copied into v1.12-docs/. - Mention how to bump the gen scripts. - Show a command that to bump versions in install instructions. - Mention manifest.json that needs to be updated. - Mention the file release-notes/README.md's index that needs updating. I also had to re-order the bullet points due to conflicts with Irbe's changes to the release process document. Apologies for the noise that these changes introduce! Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 213 ++++++++++++------- 1 file changed, 136 insertions(+), 77 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 248c640de0..cc4e008a98 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -141,7 +141,7 @@ page if a step is missing or if it is outdated. | (optional) patch pre-release[^1] | `v1.3.1-beta.0` | | patch release (or "point release") | `v1.3.1` | -[^1]: One or more "patch pre-releases" may be created to allow voluntary community testing of a bug fix or security fix before the fix is made generally available. The suffix `-beta` must be used for patch pre-releases. + [^1]: One or more "patch pre-releases" may be created to allow voluntary community testing of a bug fix or security fix before the fix is made generally available. The suffix `-beta` must be used for patch pre-releases. 2. **(final release only)** Make sure that a PR with the new upgrade document is ready to be merged on @@ -149,7 +149,103 @@ page if a step is missing or if it is outdated. example, see [upgrading-1.0-1.1](https://cert-manager.io/docs/installation/upgrading/upgrading-1.0-1.1/). -3. Check that the `origin` remote is correct. To do that, run the following +3. **(final + patch releases)** **Website Updates, part 1** (creating the "release + notes" PR). + + **⚠️ This step can be done ahead of time.** + + The steps below need to happen using `master` (**final release**) or + `release-1.x` (**patch release**). The PR will be merged after the release. + + 1. Go to the Generate `release-notes.md` using the instructions further below + (Ctrl+F and look for `github-release-description.md`). + 2. Remove the "Dependencies" section. + 3. Edit any `release-note` block in the PR description that doesn't follow + the [release-note guidelines](../contributing/contributing-flow.md#release-note-guidelines) + and copy the same change into `release-notes.md` (or re-generate the + file). + 4. Add the section "Major themes" and "Community" by taking example on the + previous release note pages. + 5. Replace the GitHub issue numbers and GitHub handles (e.g., `#1234` or + `@maelvls`) with actual links using the following command: + + ```bash + sed github-release-description.md \ + -e 's$#([0-9]+)$[#\1](https://github.com/cert-manager/cert-manager/pull/\1)$g' \ + -e 's$@(\w+)$[@\1](https://github.com/\1)$g' \ + -E \ + -i + ``` + + 6. Move `release-notes.md` to the website repo: + + ```bash + # From the cert-manager repo. + mv release-notes.md ../website/content/docs/release-notes-1.X.md + ``` + + 7. Add an entry to `content/docs/manifest.json`: + + ```diff + { + "title": "Release Notes", + "routes": [ + + { + + "title": "v1.12", + + "path": "/docs/release-notes/release-notes-1.12.md" + + }, + ``` + + 8. Add a line to the file `content/docs/release-notes/README.md`. + +4. **(final + patch release)** Prepare the "website updates" PR for the website. + + > ⚠️ This step can be done ahead of time. + + In that PR: + + 1. (**final release**) Update the section "Supported releases" in the + [supported-releases](../installation/supported-releases.md) page. + 2. (**final release**) Update the section "How we determine supported + Kubernetes versions" on the + [supported-releases](../installation/supported-releases.md) page. + 3. (**final release**) Bump the version that appears in + `scripts/gendocs/generate-new-import-path-docs`. For example: + ```diff + -LATEST_VERSION="v1.11-docs" + +LATEST_VERSION="v1.12-docs" + + -genversionwithcli "release-1.11" "$LATEST_VERSION" + +genversionwithcli "release-1.12" "$LATEST_VERSION" + ``` + + 4. (**final + patch release**) Bump all versions present in installation + instructions. To find these versions: + + ```bash + find ./content/docs/installation -name '*.md' -not -path '*/upgrad**' -exec sed -i.bak 's/1.11../1.12.0/g' '{}' \; + rm -f **/*.bak + ``` + + To check that all mentions of that versions are gone, run: + + ```bash + grep -R -n -F 'v1.11.' content/docs/installation + ``` + + 5. (**final release only**) Freeze the `docs/` folder by creating a copy + and remove the `docs/`-only folders: + ```bash + cp -r content/docs content/v1.12-docs + rm -rf content/v1.12-docs/{installation/supported-releases,installation/upgrading,release-notes} + ``` + 6. (**final + patch releases**) Update the [API docs](https://cert-manager.io/docs/reference/api-docs/) and [CLI docs](https://cert-manager.io/docs/cli//): + ```bash + # From the website repository, on the master branch. + ./scripts/gendocs/generate + ``` + +5. Check that the `origin` remote is correct. To do that, run the following command and make sure it returns the upstream `https://github.com/cert-manager/cert-manager.git`: @@ -165,7 +261,7 @@ page if a step is missing or if it is outdated. origin https://github.com/jetstack/cert-manager (push) ``` -4. Place yourself on the correct branch: +6. Place yourself on the correct branch: - **(initial alpha and subsequent alpha)**: place yourself on the `master` branch: @@ -192,7 +288,7 @@ page if a step is missing or if it is outdated. permission, you will have to open a PR to merge master into the release branch), and wait for the PR checks to become green. - - **(subsequent beta, patch release and final release)**: place yourself on + - **(subsequent beta, patch release and final release)**: place yourself on the release branch: ```bash @@ -225,7 +321,7 @@ page if a step is missing or if it is outdated. > This is only a temporary change to allow you to update the branch. > [Prow will re-apply the branch protection within 24 hours](https://docs.prow.k8s.io/docs/components/optional/branchprotector/#updating). -5. Create the required tags for the new release locally and push it upstream (starting the cert-manager build): +7. Create the required tags for the new release locally and push it upstream (starting the cert-manager build): ```bash RELEASE_VERSION=v1.8.0-beta.0 @@ -244,11 +340,12 @@ page if a step is missing or if it is outdated. kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). -6. Ensure that cmctl refers to the latest tag of cert-manager: +8. Ensure that cmctl refers to the latest tag of cert-manager: + + 1. Bump cert-manager version in [cmctl `go.mod` file](https://github.com/cert-manager/cert-manager/blob/v1.12.0/cmd/ctl/go.mod#L15) and cherry-pick the commit to the release branch. -Bump cert-manager version in [cmctl `go.mod` file](https://github.com/cert-manager/cert-manager/blob/v1.12.0/cmd/ctl/go.mod#L15) and cherry-pick the commit to the release branch. + 2. Add the tag for cmctl: -Add the tag for cmctl: ```bash # This tag is required to be able to go install cmctl # See https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 @@ -256,7 +353,11 @@ Add the tag for cmctl: git push origin "cmd/ctl/$RELEASE_VERSION" ``` -7. Generate and edit the release notes: +9. Create the description for the GitHub Release: + + > **Note:** This step is about creating the description that will be + > copy-pasted into the GitHub release page. The creation of the "Release + > Note" page on the website is done in a previous step. 1. Use the following two tables to understand how to fill in the four environment variables needed for the next step. These four environment @@ -301,8 +402,7 @@ Add the tag for cmctl: export BRANCH="release-1.3" ``` - 2. Generate `release-notes.md` at the root of your cert-manager repo folder - with the following command: + 2. Generate `github-release-description.md` with the following command: ```bash # Must be run from the cert-manager folder. @@ -312,33 +412,19 @@ Add the tag for cmctl: release-notes --debug --repo-path cert-manager \ --org cert-manager --repo cert-manager \ --required-author "jetstack-bot" \ - --output release-notes.md + --output github-release-description.md ```

      The GitHub token **does not need any scope**. The token is required only to avoid rate-limits imposed on anonymous API users.

      + 3. Add a one-sentence summary at the top. - 3. Sanity check the notes: - - - Make sure the notes contain details of all the features and bug - fixes that you expect to be in the release. - - Add additional blurb, notable items and characterize change log. - - You can see the commits that will go into this release by using the - [GitHub compare](https://github.com/cert-manager/cert-manager/compare). For - example, while releasing `v1.0.0`, you want to compare it with the - latest pre-released version `v1.0.0-beta.1`: - - ```text - https://github.com/cert-manager/cert-manager/compare/v1.0.0-beta.1...master - ``` + 4. **(final release only)** Write the section "Community" by taking example + on past GitHub Releases. - 4. **(final release only)** Check the release notes include all changes - since the last final release. - -8. Check that the build is complete and send Slack messages about the release: +10. Check that the build is complete and send Slack messages about the release: 1. For recent versions of cert-manager, the build will have been automatically triggered by the tag being pushed earlier. You can check if it's complete on @@ -370,14 +456,14 @@ Add the tag for cmctl: properly redacted but sometimes we forget to update this.

      - 3. Send a second Slack message in reply to this first message with the + 4. Send a second Slack message in reply to this first message with the Cloud Build job link. For example, the message might look like:

      Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237

      -9. Run `cmrel publish`: +11. Run `cmrel publish`: 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are valid. Run the following command: @@ -422,7 +508,7 @@ Add the tag for cmctl: Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237

      -10. Publish the GitHub release: +12. Publish the GitHub release: 1. Visit the draft GitHub release and paste in the release notes that you generated earlier. You will need to manually edit the content to match @@ -437,11 +523,11 @@ Add the tag for cmctl: 4. Click "Publish" to make the GitHub release live. -11. Merge the pull request containing the Helm chart: +13. Merge the pull request containing the Helm chart: - The Helm charts for cert-manager are served using Cloudflare pages - and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). - The `cmrel publish --nomock` step (above) will have created a PR in this repository which you now have to review and merge, as follows: + The Helm charts for cert-manager are served using Cloudflare pages + and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). + The `cmrel publish --nomock` step (above) will have created a PR in this repository which you now have to review and merge, as follows: 1. [Visit the pull request](https://github.com/jetstack/jetstack-charts/pulls) 2. Review the changes @@ -449,10 +535,12 @@ Add the tag for cmctl: 4. Merge the PR 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). -12. **(final release only)** Add the new final release to the - [supported-releases](../installation/supported-releases.md) page. +14. **(final + patch releases)** **Website Updates, part 2.** + + Proceed with merging the website PRs "release notes" and "website bump" you + have created previously. -13. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. +15. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. Assuming you have `brew` installed, you can use the `brew bump-formula-pr` command to do this. You'll need the new tag name and the commit hash of that @@ -469,7 +557,7 @@ Add the tag for cmctl: against https://github.com/homebrew/homebrew-core has been opened, continue with further release steps. -14. Post a Slack message as an answer to the first message. Toggle the check +16. Post a Slack message as an answer to the first message. Toggle the check box "Also send to `#cert-manager-dev`" so that the message is well visible. Also cross-post the message on `#cert-manager`. @@ -477,7 +565,7 @@ Add the tag for cmctl: https://github.com/cert-manager/cert-manager/releases/tag/v1.0.0 🎉

      -15. **(final release only)** Show the release to the world: +17. **(final release only)** Show the release to the world: 1. Send an email to [`cert-manager-dev@googlegroups.com`](https://groups.google.com/g/cert-manager-dev) @@ -490,7 +578,7 @@ Add the tag for cmctl: 3. Send a toot from the cert-manager Mastodon account! Login details are in Jetstack's 1password (for now). ([Example toot](https://infosec.exchange/@CertManager/109666434738850493)) -16. Proceed to the post-release steps: +18. Proceed to the post-release "testing and release" steps: 1. **(initial beta only)** Create a PR on [cert-manager/release](https://github.com/cert-manager/release) in order to @@ -500,15 +588,7 @@ Add the tag for cmctl: open a PR to [cert-manager/testing](https://github.com/jetstack/testing) adding the generated prow configs. Use [this PR](https://github.com/jetstack/testing/pull/766) as an example. - 3. If needed, open a PR to - [`cert-manager/website`](https://github.com/cert-manager/website) in - order to: - - - Update the section "How we determine supported Kubernetes versions" on - the [supported-releases](../installation/supported-releases.md) page. - - Add any new release notes, if needed. - - 4. **(final release only)** Create a PR on + 3. **(final release only)** Create a PR on [cert-manager/release](https://github.com/cert-manager/release), removing the now unsupported release version (2 versions back) in this file: @@ -518,10 +598,10 @@ Add the tag for cmctl: This will remove the periodic ProwJobs for this version as they're no longer needed. - 5. **(final release only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and + 4. **(final release only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and open a PR to [jetstack/testing](https://github.com/jetstack/testing) adding the generated prow configs. - 6. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/jetstack/testing) + 5. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/jetstack/testing) and update the [milestone_applier](https://github.com/jetstack/testing/blob/3110b68e082c3625bf0d26265be2d29e41da14b2/config/plugins.yaml#L69) config so that newly raised PRs on master are applied to a new milestone for the next release. E.g. if master currently points at the `v1.10` milestone, change it to point at `v1.11`. @@ -529,32 +609,11 @@ Add the tag for cmctl: If the [milestone](https://github.com/cert-manager/cert-manager/milestones) for the next release doesn't exist, create it first. If you consider the milestone for the version you just released to be complete, close it. - 7. **(final release only)** Open a PR to - [`cert-manager/website`](https://github.com/cert-manager/website) in - order to: - - - Update the section "Supported releases" in the - [supported-releases](../installation/supported-releases.md) page. - - Update the section "How we determine supported Kubernetes versions" on - the [supported-releases](../installation/supported-releases.md) page. - In the table, set "n/a" for the line where "next periodic" is since - these tests will be disabled until we do our first alpha. - - Update the [API docs](../reference/api-docs.md) and [CLI docs](../cli/README.md) by running `scripts/gendocs/generate` - and commit any changes to a branch and create a PR to merge those into - `master` or `release-next` depending on whether this is a minor or - patch release. - - 8. Ensure that any installation commands in - [`cert-manager/website`](https://github.com/cert-manager/website) install - the latest version. This should be done after every release, including - patch releases as we want to encourage users to always install the latest - patch. In addition, ensure that release notes for the latest version are added. - - 9. Open a PR against the Krew index such as [this one](https://github.com/kubernetes-sigs/krew-index/pull/1724), + 6. Open a PR against the Krew index such as [this one](https://github.com/kubernetes-sigs/krew-index/pull/1724), bumping the versions of our kubectl plugins. This is likely only worthwhile if cmctl / kubectl plugin functionality has changed significantly or after the first release of a new major version. - 10. Create a new OLM package and publish to OperatorHub + 7. Create a new OLM package and publish to OperatorHub cert-manager can be [installed](https://cert-manager.io/docs/installation/operator-lifecycle-manager/) using Operator Lifecycle Manager (OLM) so we need to create OLM packages for each cert-manager version and publish them to both From dd07552e00264ea50a0a75bdeab3ddcdb7b6eb5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 23 May 2023 12:38:35 +0200 Subject: [PATCH 072/264] release-process: explain how to merge release-next into master MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 32 +++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index cc4e008a98..bd5f33709f 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -143,14 +143,15 @@ page if a step is missing or if it is outdated. [^1]: One or more "patch pre-releases" may be created to allow voluntary community testing of a bug fix or security fix before the fix is made generally available. The suffix `-beta` must be used for patch pre-releases. -2. **(final release only)** Make sure that a PR with the new upgrade +2. **(final release only)** Prepare the Website "Upgrade Notes" PR. + + Make sure that a PR with the new upgrade document is ready to be merged on [cert-manager/website](https://github.com/cert-manager/website). See for example, see [upgrading-1.0-1.1](https://cert-manager.io/docs/installation/upgrading/upgrading-1.0-1.1/). -3. **(final + patch releases)** **Website Updates, part 1** (creating the "release - notes" PR). +3. **(final + patch releases)** Prepare the Website "Release Notes" PR. **⚠️ This step can be done ahead of time.** @@ -198,7 +199,7 @@ page if a step is missing or if it is outdated. 8. Add a line to the file `content/docs/release-notes/README.md`. -4. **(final + patch release)** Prepare the "website updates" PR for the website. +4. **(final + patch release)** Prepare the Website "Release Notes" PR. > ⚠️ This step can be done ahead of time. @@ -211,6 +212,7 @@ page if a step is missing or if it is outdated. [supported-releases](../installation/supported-releases.md) page. 3. (**final release**) Bump the version that appears in `scripts/gendocs/generate-new-import-path-docs`. For example: + ```diff -LATEST_VERSION="v1.11-docs" +LATEST_VERSION="v1.12-docs" @@ -235,11 +237,14 @@ page if a step is missing or if it is outdated. 5. (**final release only**) Freeze the `docs/` folder by creating a copy and remove the `docs/`-only folders: + ```bash cp -r content/docs content/v1.12-docs rm -rf content/v1.12-docs/{installation/supported-releases,installation/upgrading,release-notes} ``` + 6. (**final + patch releases**) Update the [API docs](https://cert-manager.io/docs/reference/api-docs/) and [CLI docs](https://cert-manager.io/docs/cli//): + ```bash # From the website repository, on the master branch. ./scripts/gendocs/generate @@ -535,10 +540,23 @@ page if a step is missing or if it is outdated. 4. Merge the PR 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). -14. **(final + patch releases)** **Website Updates, part 2.** +14. **(final + patch releases)** Merge the 4 Website PRs: + + 1. Merge the PRs "Release Notes", "Upgrade Notes", and "Freeze And Bump + Versions" that you have created previously. + 2. Create the PR "Merge release-next into master" by [clicking + here][ff-release-next]. + + If you see the label `dco-signoff: no`, add a comment on the PR with: + + ```text + /override dco + ``` + + This command is necessary because some the merge commits have been + written by the bot and do not have a DCO signoff. - Proceed with merging the website PRs "release notes" and "website bump" you - have created previously. + [ff-release-next]: https://github.com/cert-manager/website/compare/master...release-next?quick_pull=1&title=%5BPost-Release%5D+Merge+release-next+into+master&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco 15. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. From 0ba7b052412686f1ea0cac7cb0bc25190abe6fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 27 Feb 2023 17:13:54 +0100 Subject: [PATCH 073/264] package.json: use "npm exec" for running the "concurrently" tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 55a5892a89..40ffc6f788 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "generate:sitemap": "next-sitemap", "export": "next export", "start": "next start", - "check": "concurrently --group --timings npm:check:* # Run all the npm check:* scripts in parallel", + "check": "npm exec concurrently -y -- --group --timings npm:check:* # Run all the npm check:* scripts in parallel", "check:next-lint": "next lint", "check:links": "find content/docs -type f -name '*.md' | xargs markdown-link-check --quiet --config markdown-link-check.json 2>&1 | awk -v RS=FILE: '/ERROR/{f=1; print RS $0} END{exit f}' # Split into records based on the word FILE and print only records containing word ERROR", "check:spelling": "FORCE_COLOR=1 mdspell --report --en-us --ignore-numbers --ignore-acronyms 'content/**/*.md' 'content/**/*.html' '_layouts/*.html' '_includes/*.html' '*.html' '!**/api-docs.md' '!content/*docs/projects/approver-policy/api-reference.md' # Force color output in mdspell. # See https://github.com/lukeapage/node-markdown-spellcheck/issues/36#issuecomment-482649408 ", From e0c2085c37eb575f350d22ed52a5257628c50b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 7 Mar 2023 10:32:57 +0100 Subject: [PATCH 074/264] Revert "package.json: use "npm exec" for running the "concurrently" tool" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c399aae52becfd2780a909ce2d62e8182ad91f01. I mistakenly forgot to run "npm i" before running ./scripts/verify. Signed-off-by: Maël Valais --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40ffc6f788..55a5892a89 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "generate:sitemap": "next-sitemap", "export": "next export", "start": "next start", - "check": "npm exec concurrently -y -- --group --timings npm:check:* # Run all the npm check:* scripts in parallel", + "check": "concurrently --group --timings npm:check:* # Run all the npm check:* scripts in parallel", "check:next-lint": "next lint", "check:links": "find content/docs -type f -name '*.md' | xargs markdown-link-check --quiet --config markdown-link-check.json 2>&1 | awk -v RS=FILE: '/ERROR/{f=1; print RS $0} END{exit f}' # Split into records based on the word FILE and print only records containing word ERROR", "check:spelling": "FORCE_COLOR=1 mdspell --report --en-us --ignore-numbers --ignore-acronyms 'content/**/*.md' 'content/**/*.html' '_layouts/*.html' '_includes/*.html' '*.html' '!**/api-docs.md' '!content/*docs/projects/approver-policy/api-reference.md' # Force color output in mdspell. # See https://github.com/lukeapage/node-markdown-spellcheck/issues/36#issuecomment-482649408 ", From 7491a300cbb5063b33f4748079cf3f94b534eabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 19 Jun 2023 18:38:05 +0200 Subject: [PATCH 075/264] release-process: explain /docs -> /v1.12-docs in manifest.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- .gitignore | 5 ++++- content/docs/contributing/release-process.md | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index cb88a5aa38..51b489a0e5 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,7 @@ public/feed.* .netlify/ # IntelliJ -.idea \ No newline at end of file +.idea + +# Our release-process.md tells us to run 'sed' commands that create .bak files. +*.bak diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index bd5f33709f..e2b055a156 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -235,12 +235,14 @@ page if a step is missing or if it is outdated. grep -R -n -F 'v1.11.' content/docs/installation ``` - 5. (**final release only**) Freeze the `docs/` folder by creating a copy - and remove the `docs/`-only folders: + 5. (**final release only**) Freeze the `docs/` folder by creating a copy , + removing the pages from that copy that don't make sense to be versionned, + and updating the `manifest.json` file: ```bash cp -r content/docs content/v1.12-docs rm -rf content/v1.12-docs/{installation/supported-releases,installation/upgrading,release-notes} + sed -i.bak 's|docs|v1.12-docs|g' content/v1.12-docs/manifest.json ``` 6. (**final + patch releases**) Update the [API docs](https://cert-manager.io/docs/reference/api-docs/) and [CLI docs](https://cert-manager.io/docs/cli//): From 8690cf7246cee5563c22bf06e3a53a3bfaaa6775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 19 Jun 2023 18:48:49 +0200 Subject: [PATCH 076/264] release-process: typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index e2b055a156..162076c63e 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -236,7 +236,7 @@ page if a step is missing or if it is outdated. ``` 5. (**final release only**) Freeze the `docs/` folder by creating a copy , - removing the pages from that copy that don't make sense to be versionned, + removing the pages from that copy that don't make sense to be versioned, and updating the `manifest.json` file: ```bash From df659d75683bf5540d073efb445ba846f3d7d644 Mon Sep 17 00:00:00 2001 From: Peter Fiddes Date: Mon, 19 Jun 2023 18:06:23 +0100 Subject: [PATCH 077/264] docs: Add considerations for CRD installation Signed-off-by: Peter Fiddes --- .spelling | 1 + content/docs/installation/helm.md | 89 +++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/.spelling b/.spelling index 3580cb7ec8..24047110b9 100644 --- a/.spelling +++ b/.spelling @@ -601,6 +601,7 @@ v4.4.1 liveness apiservices arm64 +IaC # TEMPORARY # these are temporarily ignored because the spellchecker diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index 18a7af9695..eb3da6c977 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -18,7 +18,6 @@ non-namespaced resources in your cluster and care must be taken to ensure that i ### Steps - #### 1. Add the Helm repository This repository is the only supported source of cert-manager charts. There are some other mirrors and copies across the internet, but those are entirely unofficial and could present a security risk. @@ -38,10 +37,14 @@ helm repo update #### 3. Install `CustomResourceDefinitions` cert-manager requires a number of CRD resources, which can be installed manually using `kubectl`, -or using the `installCRDs` option when installing the Helm chart. +or using the `installCRDs` option when installing the Helm chart. Both options +are described below and will achieve the same result but with varying +consequences. You should consult the [CRD Considerations](#crd-considerations) +section below for details on each method. ##### Option 1: installing CRDs with `kubectl` +> Recommended for production installations ```bash kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.crds.yaml @@ -49,6 +52,8 @@ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/ ##### Option 2: install CRDs as part of the Helm release +> Recommended for ease of use & compatibility + To automatically install and manage the CRDs as part of your Helm release, you must add the `--set installCRDs=true` flag to your Helm installation command. @@ -114,12 +119,15 @@ dependencies: alias: cert-manager condition: cert-manager.enabled ``` -You can then override the namespace in 2 ways + +You can then override the namespace in 2 ways: + 1. In `Values.yaml` file ```yaml cert-manager: #defined by either the name or alias of your dependency in Chart.yaml namespace: security ``` + 2. In the helm command using `--set` ```bash helm install example example_chart \ @@ -171,7 +179,6 @@ Uninstalling cert-manager from a `helm` installation is a case of running the installation process, *in reverse*, using the delete command on both `kubectl` and `helm`. - ```bash helm --namespace cert-manager delete cert-manager ``` @@ -193,6 +200,10 @@ using the link to the version `vX.Y.Z` you installed: kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/vX.Y.Z/cert-manager.crds.yaml ``` +*Note:* If you used `helm` to install the CRDs with the `installCRDs=true` +value for the chart, then the CRDs will have been automatically removed and +you do not need to run this final `kubectl` command. + ### Namespace Stuck in Terminating State If the namespace has been marked for deletion without deleting the cert-manager @@ -205,3 +216,73 @@ experiencing issues then run: ```bash kubectl delete apiservice v1beta1.webhook.cert-manager.io ``` + +## CRD considerations + +### kubectl installation + +When installing CRDs with `kubectl`, you will need to upgrade these in tandem +with your cert-manager installation upgrades. This approach may be useful when +you do not have the ability to install CRDs all the time in your environment. +If you do not upgrade these as you upgrade cert-manager itself, you may miss +out on new features for cert-manager. + +Benefits: + +- CRDs will not change once applied + +Drawbacks: + +- CRDs are not automatically updated and need to be reapplied before + upgrading cert-manager +- You may have different installation processes for CRDs compared to + the other resources. + +### helm installation + +cert-manager **does not use** the [official helm method](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/ ) +of installing CRD resources. This is because it makes upgrading CRDs +impossible with `helm` CLI alone. The helm team explain the limitations +of their approach [here](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations). + +cert-manager actually bundles the CRDs along with the other templates +in the Helm chart. This means that Helm manages these resources so they are +upgraded with your cert-manager release when you use +`installCRDs: true` in your values file or CLI command. This does also mean +that if you uninstall the release, the CRDs will also be uninstalled. If that +happens then you will loose all instances of those CRDs, e.g. all `Certificate` +resources in the cluster. You should consider if this is likely to happen to +you and have a mitigation, such as +[backups](https://cert-manager.io/docs/tutorials/backup/#backing-up-cert-manager-resource-configuration) +or a means to reapply resources from an Infrastructure as Code (IaC) pattern. + +**Note** this also means a typo like `installCRD: true` would be an invalid +value and helm would silently ignore this and remove the CRDs when you next +run your `helm upgrade`. + +Benefits: + +- CRDS are automatically updated when you upgrade cert-manager via `helm` +- Same action manages both CRDs and other installation resources + +Drawbacks: + +- If you uninstall cert-manager, the CRDs are also uninstalled. +- Helm values need to be correct to avoid accidental removal of CRDs. + +### CRD Installation Advice + +> You should follow the path that makes sense for your environment. + +Generally we recommend: + +- For **Safety**, install CRDs outside of Helm, e.g. `kubectl` +- For **Ease of use**, install CRDS with `helm` + +You may want to consider your approach along with other tools that may offer +helm compatible installs, for a standardized approach to managing CRD +resources. If you have an approach that cert-manager does not currently +support, then please +[raise an issue](https://github.com/cert-manager/cert-manager/issues) to +discuss. + From 093f3dfc9085cf4c59f0e191b12841ac1675b384 Mon Sep 17 00:00:00 2001 From: Peter Fiddes Date: Fri, 14 Apr 2023 17:31:25 +0100 Subject: [PATCH 078/264] feat: Public trust management tutorial Signed-off-by: Peter Fiddes --- content/docs/manifest.json | 4 + .../README.md | 605 ++++++++++++++++++ .../gatekeeper/deploy-novol.yaml | 29 + .../gatekeeper/deploy-withvol.yaml | 38 ++ .../gatekeeper-trust-pod-ca-volume.yaml | 29 + .../gatekeeper-trust-pod-ca-volumemount.yaml | 24 + .../trust/bundle-one-ca.yaml | 39 ++ .../trust/bundle-public.yaml | 13 + .../trust/deploy-auto.yaml | 40 ++ 9 files changed, 821 insertions(+) create mode 100644 content/docs/tutorials/getting-started-with-trust-manager/README.md create mode 100644 content/docs/tutorials/getting-started-with-trust-manager/gatekeeper/deploy-novol.yaml create mode 100644 content/docs/tutorials/getting-started-with-trust-manager/gatekeeper/deploy-withvol.yaml create mode 100644 content/docs/tutorials/getting-started-with-trust-manager/gatekeeper/gatekeeper-trust-pod-ca-volume.yaml create mode 100644 content/docs/tutorials/getting-started-with-trust-manager/gatekeeper/gatekeeper-trust-pod-ca-volumemount.yaml create mode 100644 content/docs/tutorials/getting-started-with-trust-manager/trust/bundle-one-ca.yaml create mode 100644 content/docs/tutorials/getting-started-with-trust-manager/trust/bundle-public.yaml create mode 100644 content/docs/tutorials/getting-started-with-trust-manager/trust/deploy-auto.yaml diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 2c99b44c50..fe17a201cd 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -428,6 +428,10 @@ { "title": "Securing Ingresses with ZeroSSL", "path": "/docs/tutorials/zerossl/zerossl.md" + }, + { + "title": "Managing Public Trust in Kubernetes with Trust Manager", + "path": "/docs/tutorials/getting-started-with-trust-manager/README.md" } ] }, diff --git a/content/docs/tutorials/getting-started-with-trust-manager/README.md b/content/docs/tutorials/getting-started-with-trust-manager/README.md new file mode 100644 index 0000000000..901fac4b72 --- /dev/null +++ b/content/docs/tutorials/getting-started-with-trust-manager/README.md @@ -0,0 +1,605 @@ +--- +title: Managing Public Trust in Kubernetes with Trust Manager +description: Learn how to deploy and configure trust-manager to automatically distribute your approved Public CA configuration to you entire Kubernetes cluster. +--- + +*Last Verified: 14 April 2023* + + + +In this tutorial we will walk through how we can use +[trust-manager](https://cert-manager.io/docs/projects/trust-manager/) to +distribute publicly trusted Certificate Authority (CA) certificates inside +a Kubernetes cluster. Once distributed we will also show: + +- How you can automatically reload applications when your trust bundle changes +- How you can enforce applications to use your distributed CA bundle + +From there we will use a simple `curl` pod to show to automatically mount the +trusted CA `Bundle`, so it can be used without having to configure curl +manually. This mimics how an application would not need any additional +configuration to make use of your trusted CA certificates bundle. + +In this tutorial we will be limiting the scope of our changes to only impact +the `team-a` namespace. To get the most out of these features you will want to +remove this limitation. + +## Prerequisites + +**💻 Knowledge** + +For this tutorial we assume that you know about +[trust-manager](https://cert-manager.io/docs/projects/trust-manager/) already, +and you are aware of how it distributes CA certificate from a `Bundle` into +`ConfigMap` resources across the cluster. If not then check out +[the documentation](https://cert-manager.io/docs/projects/trust-manager/) +for a good understanding. + +**💻 Software** + +1. [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl): The Kubernetes +command-line tool which allows you to configure Kubernetes clusters. +1. [helm](https://helm.sh/): A packge manager for Kubernetes +1. [yq](https://github.com/mikefarah/yq#install): A command line tool for +parsing yaml with helpful coloring + +## Distribute Public CA Trust + +### Setup Application & Bundle + +1) Ensure you have [trust-manager](https://cert-manager.io/docs/projects/trust/#installation) installed. If not simply use: + + ```shell + helm repo add jetstack https://charts.jetstack.io + helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set installCRDs=true --wait --create-namespace + helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --wait + ``` + +1) Create your first `Bundle` resource including only Public CA certificates + + ```yaml file=./trust/bundle-public.yaml + ``` + + ```shell + kubectl apply -f - < ..data/ca-certificates.crt + ``` + + Note that normally this container image the output would look something + like the following, when there is no volume overriding this directory: + + ``` + ~ $ ls -ltr /etc/ssl/certs/ + total 608 + -rw-r--r-- 1 root root 214222 Apr 14 01:11 ca-certificates.crt + lrwxrwxrwx 1 root root 52 Apr 14 01:11 ca-cert-vTrus_Root_CA.pem -> /usr/share/ca-certificates/mozilla/vTrus_Root_CA.crt + lrwxrwxrwx 1 root root 56 Apr 14 01:11 ca-cert-vTrus_ECC_Root_CA.pem -> /usr/share/ca-certificates/mozilla/vTrus_ECC_Root_CA.crt + ... + lrwxrwxrwx 1 root root 53 Apr 14 01:11 02265526.0 -> ca-cert-Entrust_Root_Certification_Authority_-_G2.pem + lrwxrwxrwx 1 root root 31 Apr 14 01:11 002c0b4f.0 -> ca-cert-GlobalSign_Root_R46.pem + ``` + +1) Make a HTTPS call out to a well known site to validate `curl` works without +having to pass the additional `--cacert` flag: + + ```shell + curl -v https://bbc.co.uk/news + ``` + + Success will result in a valid TLS connection such as: + + ``` + * Trying 151.101.0.81:443... + * Connected to bbc.co.uk (151.101.0.81) port 443 (#0) + * ALPN: offers h2,http/1.1 + * TLSv1.3 (OUT), TLS handshake, Client hello (1): + * CAfile: /etc/ssl/certs/ca-certificates.crt + * CApath: none + * TLSv1.3 (IN), TLS handshake, Server hello (2): + * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): + * TLSv1.3 (IN), TLS handshake, Certificate (11): + * TLSv1.3 (IN), TLS handshake, CERT verify (15): + * TLSv1.3 (IN), TLS handshake, Finished (20): + * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): + * TLSv1.3 (OUT), TLS handshake, Finished (20): + * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 + * ALPN: server accepted h2 + * Server certificate: + * subject: C=GB; ST=London; L=London; O=BRITISH BROADCASTING CORPORATION; CN=www.bbc.com + * start date: Mar 14 06:16:13 2023 GMT + * expire date: Apr 14 06:16:12 2024 GMT + * subjectAltName: host "bbc.co.uk" matched cert's "bbc.co.uk" + * issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign RSA OV SSL CA 2018 + * SSL certificate verify ok. + ``` + +1. Exit the container: `exit` + +## Configure Real Applications + +Based on the example above, kubernetes is able to mount over the top of the +default CA certificate bundle. You can use this with applications assuming you +know where the default locations they retrieve CA certificates from. + +For example with `Go` your application is configurable with either +`SSL_CERT_FILE` or `SSL_CERT_DIR` to point to the default CA certificate +file location. + +See more details [here](https://go.dev/src/crypto/x509/root_unix.go) and +for the default locations on various OS bases, check +[here](https://go.dev/src/crypto/x509/root_linux.go) + +```go +// Possible certificate files; stop after finding one. +var certFiles = []string{ + "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. + "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6 + "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/pki/tls/cacert.pem", // OpenELEC + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 + "/etc/ssl/cert.pem", // Alpine Linux +} + +// Possible directories with certificate files; all will be read. +var certDirectories = []string{ + "/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139 + "/etc/pki/tls/certs", // Fedora/RHEL + "/ +``` + +Having checked Python the `ssl` library uses the same two environment variables +for finding the trusted CAs: `SSL_CERT_DIR` and / or `SSL_CERT_FILE`. You can +verify this [in documentation](https://docs.python.org/3/library/ssl.html#ssl.get_default_verify_paths) +and from a python3 runtime: + +```python3 +>>> import ssl +>>> ssl.get_default_verify_paths() +DefaultVerifyPaths(cafile=None, capath='/usr/lib/ssl/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/usr/lib/ssl/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/usr/lib/ssl/certs') +``` + +This should mean that any CAs mounted in a file and any of the following files +will be trusted by any python application runtime, similar to `Go`: + +- '/usr/lib/ssl/cert.pem' +- '/usr/lib/ssl/certs/*' + +Similar could be achieved with other languages. + +## Automate and Enforce + +So now we have mounted trust-manager's bundle manually, you might be thinking: + +- What happens if the CA Bundle is changed, how do I get that change to my +application? +- How do I ensure that my CA Bundle is mounted to all applications in my +cluster without having to request changes from my tenants? + +Let's tackle both of these scenarios using additional Open Source tools. + +### Rollout CA Bundle Changes + +If your CA bundle changes, those changes will be synced to the namespaces +pretty quickly. This change will be reflected in the volume attached to the +container, but most applications will not pickup on the filesystem change. +The common approach is restarting the client application deployment, through +the use of `kubectl rollout restart deployment `. There is an +option to automate this process through a third party piece of open-source +software. + +Using [Stakater Reloader](https://github.com/stakater/Reloader) it is +possible to reload or rollout a deployment whenever a `ConfigMap` or `Secret` +changes. So whenever the `Bundle`'s target is synced, the Reloader component +can pick up this change and rollout applications mounting those resource +as volumes or environment variables. + +**Please note** that there are many alternative pieces of software that you +could bundle or write into your application container. They would simply watch +the filesystem for changes and trigger a reload of the application process. +Such an approach requires container image or code changes and this could be +difficult to implement with many tenants. The advantage to using ,reloader here +is that it's a generic solution applicable to all applications running in a +cluster. + +1. Continuing with the reloader, it can be installed with helm: + + ```shell + helm repo add stakater https://stakater.github.io/stakater-charts + helm repo update + helm install reloader stakater/reloader -n stakater-reloader --create-namespace --set fullnameOverride=reloader + ``` + +1. We can reuse the deployment `sleep-auto` from the previous section and +configured it to enabled the reload functionality: + + ```shell + kubectl annotate deployment -n team-a sleep-auto reloader.stakater.com/auto="true" + ``` + + **Please note** there are several configuration options to configure + the reloader tooling and this is only the most basic example. Refer to + [the documentation](https://github.com/stakater/Reloader#how-to-use-reloader) + for more detailed examples. + +1. In another terminal watch the application rollout: + + ```shell + kubectl get po -n team-a -w + ``` + +1. To test this change we can edit our `Bundle` resource to remove all the +default Public CA certificates and only provide one CA certificate instead: + + ```yaml file=./trust/bundle-one-ca.yaml + ``` + + ```shell + kubectl apply -f - < Date: Fri, 14 Apr 2023 17:43:07 +0100 Subject: [PATCH 079/264] fix: Remove commented out markdown Signed-off-by: Peter Fiddes --- .../tutorials/getting-started-with-trust-manager/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/tutorials/getting-started-with-trust-manager/README.md b/content/docs/tutorials/getting-started-with-trust-manager/README.md index 901fac4b72..32089987ec 100644 --- a/content/docs/tutorials/getting-started-with-trust-manager/README.md +++ b/content/docs/tutorials/getting-started-with-trust-manager/README.md @@ -5,9 +5,6 @@ description: Learn how to deploy and configure trust-manager to automatically di *Last Verified: 14 April 2023* - - In this tutorial we will walk through how we can use [trust-manager](https://cert-manager.io/docs/projects/trust-manager/) to distribute publicly trusted Certificate Authority (CA) certificates inside @@ -25,6 +22,9 @@ In this tutorial we will be limiting the scope of our changes to only impact the `team-a` namespace. To get the most out of these features you will want to remove this limitation. +**Note:** All resources provided are demonstrative and should be reviewed +properly before using in production environments. + ## Prerequisites **💻 Knowledge** From 8ec08053e9633b5d5cfec4b93c1d2fd6fba645a9 Mon Sep 17 00:00:00 2001 From: Peter Fiddes Date: Fri, 14 Apr 2023 18:01:15 +0100 Subject: [PATCH 080/264] fix: Spelling and style Signed-off-by: Peter Fiddes --- .spelling | 10 ++++++++ .../README.md | 24 +++++++++---------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.spelling b/.spelling index 24047110b9..7cd8fbf5a0 100644 --- a/.spelling +++ b/.spelling @@ -602,6 +602,16 @@ liveness apiservices arm64 IaC +Kyverno +Stakater +Reloader +reloader +yq +usr +ssl +cert.pem +Rollout +rollout # TEMPORARY # these are temporarily ignored because the spellchecker diff --git a/content/docs/tutorials/getting-started-with-trust-manager/README.md b/content/docs/tutorials/getting-started-with-trust-manager/README.md index 32089987ec..bf58a570a7 100644 --- a/content/docs/tutorials/getting-started-with-trust-manager/README.md +++ b/content/docs/tutorials/getting-started-with-trust-manager/README.md @@ -33,22 +33,22 @@ For this tutorial we assume that you know about [trust-manager](https://cert-manager.io/docs/projects/trust-manager/) already, and you are aware of how it distributes CA certificate from a `Bundle` into `ConfigMap` resources across the cluster. If not then check out -[the documentation](https://cert-manager.io/docs/projects/trust-manager/) +[the documentation](../../projects/trust-manager/README.md) for a good understanding. **💻 Software** 1. [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl): The Kubernetes command-line tool which allows you to configure Kubernetes clusters. -1. [helm](https://helm.sh/): A packge manager for Kubernetes +1. [helm](https://helm.sh/): A package manager for Kubernetes. 1. [yq](https://github.com/mikefarah/yq#install): A command line tool for -parsing yaml with helpful coloring +parsing YAML with helpful coloring. ## Distribute Public CA Trust ### Setup Application & Bundle -1) Ensure you have [trust-manager](https://cert-manager.io/docs/projects/trust/#installation) installed. If not simply use: +1) Ensure you have [trust-manager](../../projects/trust-manager/README.md#installation) installed. If not simply use: ```shell helm repo add jetstack https://charts.jetstack.io @@ -243,7 +243,7 @@ having to pass the additional `--cacert` flag: ## Configure Real Applications -Based on the example above, kubernetes is able to mount over the top of the +Based on the example above, Kubernetes is able to mount over the top of the default CA certificate bundle. You can use this with applications assuming you know where the default locations they retrieve CA certificates from. @@ -307,7 +307,7 @@ Let's tackle both of these scenarios using additional Open Source tools. If your CA bundle changes, those changes will be synced to the namespaces pretty quickly. This change will be reflected in the volume attached to the -container, but most applications will not pickup on the filesystem change. +container, but most applications will not pickup on the file system change. The common approach is restarting the client application deployment, through the use of `kubectl rollout restart deployment `. There is an option to automate this process through a third party piece of open-source @@ -321,7 +321,7 @@ as volumes or environment variables. **Please note** that there are many alternative pieces of software that you could bundle or write into your application container. They would simply watch -the filesystem for changes and trigger a reload of the application process. +the file system for changes and trigger a reload of the application process. Such an approach requires container image or code changes and this could be difficult to implement with many tenants. The advantage to using ,reloader here is that it's a generic solution applicable to all applications running in a @@ -444,8 +444,8 @@ one CA certificate `ca-certificates.crt` file, the one we have just applied: -----END CERTIFICATE----- ``` -1. The CA output here is the one that is trust by the website bbc.co.uk. We -can validate this by using `curl` from that container: +1. The CA output here is the one that is trust by the website +`https://bbc.co.uk`. We can validate this by using `curl` from that container: ```shell kubectl exec -ti -n team-a $(kubectl get po -n team-a -l app=sleep-auto -o jsonpath='{.items[0].metadata.name}') -- curl -v https://bbc.co.uk @@ -553,7 +553,7 @@ publicly trusted website, for example: `https://bbc.co.uk` kubectl exec -n team-a -ti $(kubectl get pod -n team-a -l app=test-assign -o jsonpath='{.items[0].metadata.name}') -- curl -v https://bbc.co.uk ``` - Success looks like a valid 200 response from the webpage. + Success looks like a valid 200 response from the web page. Note that this should now work without any additional configuration. If you get an SSL error at the point, check that the correct `configMap` is @@ -572,7 +572,7 @@ no action is taken if the relevant configuration is already present: ``` **Note:** If you have problems with your `Assign` policy resource, try checking -the kubernetes events (`kubectl get events`) for issues. +the Kubernetes events (`kubectl get events`) for issues. ## Public Trust with trust-manager @@ -585,7 +585,7 @@ Whilst this may appear to be more work for something that currently "works" in your environment, consider how this solution positions you to handle situations where you no longer trust a particular Certificate Authority. -Next time we will look at how simple it is to integrate Prviate Certificate +Next time we will look at how simple it is to integrate Private Certificate Authorities into this trust management process. ## Cleanup From ce9c1119e78781507c644233b12ee49586b3e40c Mon Sep 17 00:00:00 2001 From: Peter Fiddes Date: Fri, 14 Apr 2023 18:09:59 +0100 Subject: [PATCH 081/264] fix: Spelling python3 Signed-off-by: Peter Fiddes --- .../docs/tutorials/getting-started-with-trust-manager/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/tutorials/getting-started-with-trust-manager/README.md b/content/docs/tutorials/getting-started-with-trust-manager/README.md index bf58a570a7..dd2c4d9ff1 100644 --- a/content/docs/tutorials/getting-started-with-trust-manager/README.md +++ b/content/docs/tutorials/getting-started-with-trust-manager/README.md @@ -276,7 +276,7 @@ var certDirectories = []string{ Having checked Python the `ssl` library uses the same two environment variables for finding the trusted CAs: `SSL_CERT_DIR` and / or `SSL_CERT_FILE`. You can verify this [in documentation](https://docs.python.org/3/library/ssl.html#ssl.get_default_verify_paths) -and from a python3 runtime: +and from a `python3` runtime: ```python3 >>> import ssl From 1a030bbeddef9b689697138af731328772e09cf6 Mon Sep 17 00:00:00 2001 From: Peter Fiddes Date: Mon, 19 Jun 2023 18:29:26 +0100 Subject: [PATCH 082/264] docs: Review comments and some minor tweaks to flow Signed-off-by: Peter Fiddes --- .../README.md | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/content/docs/tutorials/getting-started-with-trust-manager/README.md b/content/docs/tutorials/getting-started-with-trust-manager/README.md index dd2c4d9ff1..444eca7a71 100644 --- a/content/docs/tutorials/getting-started-with-trust-manager/README.md +++ b/content/docs/tutorials/getting-started-with-trust-manager/README.md @@ -1,9 +1,9 @@ --- title: Managing Public Trust in Kubernetes with Trust Manager -description: Learn how to deploy and configure trust-manager to automatically distribute your approved Public CA configuration to you entire Kubernetes cluster. +description: Learn how to deploy and configure trust-manager to automatically distribute your approved Public CA configuration to your entire Kubernetes cluster. --- -*Last Verified: 14 April 2023* +*Last Verified: 19 June 2023* In this tutorial we will walk through how we can use [trust-manager](https://cert-manager.io/docs/projects/trust-manager/) to @@ -13,8 +13,8 @@ a Kubernetes cluster. Once distributed we will also show: - How you can automatically reload applications when your trust bundle changes - How you can enforce applications to use your distributed CA bundle -From there we will use a simple `curl` pod to show to automatically mount the -trusted CA `Bundle`, so it can be used without having to configure curl +From there we will use a simple `curl` pod to show how to automatically mount +the trusted CA `Bundle`, so it can be used without having to configure curl manually. This mimics how an application would not need any additional configuration to make use of your trusted CA certificates bundle. @@ -22,20 +22,11 @@ In this tutorial we will be limiting the scope of our changes to only impact the `team-a` namespace. To get the most out of these features you will want to remove this limitation. -**Note:** All resources provided are demonstrative and should be reviewed -properly before using in production environments. +> **Note:** All resources provided are demonstrative and should be reviewed + properly before using in production environments. ## Prerequisites -**💻 Knowledge** - -For this tutorial we assume that you know about -[trust-manager](https://cert-manager.io/docs/projects/trust-manager/) already, -and you are aware of how it distributes CA certificate from a `Bundle` into -`ConfigMap` resources across the cluster. If not then check out -[the documentation](../../projects/trust-manager/README.md) -for a good understanding. - **💻 Software** 1. [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl): The Kubernetes @@ -46,6 +37,9 @@ parsing YAML with helpful coloring. ## Distribute Public CA Trust +Let us first setup trust-manager and have our public CAs distributed to our +demo namespace: `team-a`. + ### Setup Application & Bundle 1) Ensure you have [trust-manager](../../projects/trust-manager/README.md#installation) installed. If not simply use: @@ -79,7 +73,7 @@ parsing YAML with helpful coloring. EOF ``` -1) Lets create a namespace where our application will run: +1) Let's create a namespace where our application will run: ```shell kubectl apply -f - < Date: Tue, 20 Jun 2023 14:40:39 +0100 Subject: [PATCH 083/264] docs: Correction and warnings on CA Paths Signed-off-by: Peter Fiddes --- .../README.md | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/content/docs/tutorials/getting-started-with-trust-manager/README.md b/content/docs/tutorials/getting-started-with-trust-manager/README.md index 444eca7a71..999a84a141 100644 --- a/content/docs/tutorials/getting-started-with-trust-manager/README.md +++ b/content/docs/tutorials/getting-started-with-trust-manager/README.md @@ -19,8 +19,8 @@ manually. This mimics how an application would not need any additional configuration to make use of your trusted CA certificates bundle. In this tutorial we will be limiting the scope of our changes to only impact -the `team-a` namespace. To get the most out of these features you will want to -remove this limitation. +the `team-a` namespace. To get the most out of these features you will want +to remove this limitation. > **Note:** All resources provided are demonstrative and should be reviewed properly before using in production environments. @@ -42,13 +42,8 @@ demo namespace: `team-a`. ### Setup Application & Bundle -1) Ensure you have [trust-manager](../../projects/trust-manager/README.md#installation) installed. If not simply use: - - ```shell - helm repo add jetstack https://charts.jetstack.io - helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set installCRDs=true --wait --create-namespace - helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --wait - ``` +1) Install trust-manager following the + [instructions here](../../projects/trust-manager/README.md#installation). 1) Create your first `Bundle` resource including only Public CA certificates @@ -81,7 +76,6 @@ demo namespace: `team-a`. kind: Namespace metadata: labels: - kubernetes.io/metadata.name: team-a trust: enabled name: team-a EOF @@ -96,8 +90,11 @@ demo namespace: `team-a`. trust: enabled ``` + > **Note**: this is to limit the scope of our trust bundle to only the + `team-a` namespace as mentioned previously. + 1) Verify that the trust-manager controller has correctly propagated the -CA bundle to the namespace: + CA bundle to the namespace: ```shell kubectl get configmap -n team-a public-bundle -o yaml @@ -108,13 +105,19 @@ CA bundle to the namespace: ### Mount Trust Bundle to Application with Automatic Use -Now we will mount our trusted CAs to the application in the default location -that most applications expect to find a `ca-certificates.crt` file. The benefit -to this approach is that any application code inside the container will use -this file by default and not require any additional configuration. There is -the added benefit that you will be mounting over the top of -`/etc/ssl/certs` which will remove any existing CA certificates usually -present from a container base image. +To use our trusted CAs we will mount them to the application in a default +location that most applications expect to find a `ca-certificates.crt` file. +The benefit to this approach is that most application code inside the container +will use this file by default and not require any additional configuration. There is the added benefit that you will be mounting over the top of +`/etc/ssl/certs` which will remove existing CA certificates, usually +present from a container base image or pulled in during CI builds. + +> **WARNING:** We have chosen one well known location in this example which + is used by alpine and `curl` for sourcing trusted CAs. This is not the only + location that can be used, so a container may have other default locations. + If you want to see where default CAs are located you can use + [paranoia](https://github.com/jetstack/paranoia) to inspect a built container + image. 1) Apply the application deployment: @@ -167,7 +170,7 @@ present from a container base image. 1) Create a shell inside the running pod: ```shell - kubectl exec -n team-a -ti $(k get po -n team-a -l app=sleep-auto -o jsonpath='{.items[0].metadata.name}') -- /bin/sh + kubectl exec -n team-a -ti $(kubectl get po -n team-a -l app=sleep-auto -o jsonpath='{.items[0].metadata.name}') -- /bin/sh ``` 1) List the contents of `/etc/ssl/certs/` to validate that only your From 647eff8b588fc5f4237c88618ecd175f1892a7a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Mon, 19 Jun 2023 18:44:17 +0200 Subject: [PATCH 084/264] post-release 1.12: https://cert-manager.io/v1.12-docs didn't work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I forgot to change the manifest.json when releasing v1.12. The command is: sed -i.bak 's|/docs/|/v1.12-docs/|g' content/v1.12-docs/manifest.json I also edited the release process document to reflect that in another PR. Signed-off-by: Maël Valais --- content/v1.12-docs/manifest.json | 326 +++++++++++++++---------------- 1 file changed, 163 insertions(+), 163 deletions(-) diff --git a/content/v1.12-docs/manifest.json b/content/v1.12-docs/manifest.json index e8b2685a6e..39f9292d84 100644 --- a/content/v1.12-docs/manifest.json +++ b/content/v1.12-docs/manifest.json @@ -6,187 +6,187 @@ "routes": [ { "title": "Introduction", - "path": "/docs/README.md" + "path": "/v1.12-docs/README.md" }, { "title": "Getting Started", - "path": "/docs/getting-started/README.md" + "path": "/v1.12-docs/getting-started/README.md" }, { "title": "Installation", "routes": [ { "title": "Introduction", - "path": "/docs/installation/README.md" + "path": "/v1.12-docs/installation/README.md" }, { "title": "Supported Releases", - "path": "/docs/installation/supported-releases.md" + "path": "/v1.12-docs/installation/supported-releases.md" }, { "title": "Cloud Compatibility", - "path": "/docs/installation/compatibility.md" + "path": "/v1.12-docs/installation/compatibility.md" }, { "title": "kubectl apply", - "path": "/docs/installation/kubectl.md" + "path": "/v1.12-docs/installation/kubectl.md" }, { "title": "Helm", - "path": "/docs/installation/helm.md" + "path": "/v1.12-docs/installation/helm.md" }, { "title": "OperatorHub (OLM)", - "path": "/docs/installation/operator-lifecycle-manager.md" + "path": "/v1.12-docs/installation/operator-lifecycle-manager.md" }, { "title": "Other tools", - "path": "/docs/installation/other-tools.md" + "path": "/v1.12-docs/installation/other-tools.md" }, { "title": "Verifying", - "path": "/docs/installation/verify.md" + "path": "/v1.12-docs/installation/verify.md" }, { "title": "Feature flags", - "path": "/docs/installation/featureflags.md" + "path": "/v1.12-docs/installation/featureflags.md" }, { "title": "Upgrading", "routes": [ { "title": "Introduction", - "path": "/docs/installation/upgrading/README.md" + "path": "/v1.12-docs/installation/upgrading/README.md" }, { "title": "Notes on Ingress Class Compatibility", - "path": "/docs/installation/upgrading/ingress-class-compatibility.md" + "path": "/v1.12-docs/installation/upgrading/ingress-class-compatibility.md" }, { "title": "Migrating Deprecated API Resources", - "path": "/docs/installation/upgrading/remove-deprecated-apis.md" + "path": "/v1.12-docs/installation/upgrading/remove-deprecated-apis.md" }, { "title": "v1.10 to v1.11", - "path": "/docs/installation/upgrading/upgrading-1.10-1.11.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.10-1.11.md" }, { "title": "v1.9 to v1.10", - "path": "/docs/installation/upgrading/upgrading-1.9-1.10.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.9-1.10.md" }, { "title": "v1.8 to v1.9", - "path": "/docs/installation/upgrading/upgrading-1.8-1.9.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.8-1.9.md" }, { "title": "v1.7 to v1.8", - "path": "/docs/installation/upgrading/upgrading-1.7-1.8.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.7-1.8.md" }, { "title": "v1.6 to v1.7", - "path": "/docs/installation/upgrading/upgrading-1.6-1.7.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.6-1.7.md" }, { "title": "v1.5 to v1.6", - "path": "/docs/installation/upgrading/upgrading-1.5-1.6.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.5-1.6.md" }, { "title": "v1.4 to v1.5", - "path": "/docs/installation/upgrading/upgrading-1.4-1.5.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.4-1.5.md" }, { "title": "v1.3 to v1.4", - "path": "/docs/installation/upgrading/upgrading-1.3-1.4.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.3-1.4.md" }, { "title": "v1.2 to v1.3", - "path": "/docs/installation/upgrading/upgrading-1.2-1.3.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.2-1.3.md" }, { "title": "v1.1 to v1.2", - "path": "/docs/installation/upgrading/upgrading-1.1-1.2.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.1-1.2.md" }, { "title": "v1.0 to v1.1", - "path": "/docs/installation/upgrading/upgrading-1.0-1.1.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-1.0-1.1.md" }, { "title": "v0.16 to v1.0", - "path": "/docs/installation/upgrading/upgrading-0.16-1.0.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.16-1.0.md" }, { "title": "v0.15 to v0.16", - "path": "/docs/installation/upgrading/upgrading-0.15-0.16.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.15-0.16.md" }, { "title": "v0.14 to v0.15", - "path": "/docs/installation/upgrading/upgrading-0.14-0.15.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.14-0.15.md" }, { "title": "v0.13 to v0.14", - "path": "/docs/installation/upgrading/upgrading-0.13-0.14.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.13-0.14.md" }, { "title": "v0.12 to v0.13", - "path": "/docs/installation/upgrading/upgrading-0.12-0.13.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.12-0.13.md" }, { "title": "v0.11 to v0.12", - "path": "/docs/installation/upgrading/upgrading-0.11-0.12.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.11-0.12.md" }, { "title": "v0.10 to v0.11", - "path": "/docs/installation/upgrading/upgrading-0.10-0.11.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.10-0.11.md" }, { "title": "v0.9 to v0.10", - "path": "/docs/installation/upgrading/upgrading-0.9-0.10.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.9-0.10.md" }, { "title": "v0.8 to v0.9", - "path": "/docs/installation/upgrading/upgrading-0.8-0.9.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.8-0.9.md" }, { "title": "v0.7 to v0.8", - "path": "/docs/installation/upgrading/upgrading-0.7-0.8.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.7-0.8.md" }, { "title": "v0.6 to v0.7", - "path": "/docs/installation/upgrading/upgrading-0.6-0.7.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.6-0.7.md" }, { "title": "v0.5 to v0.6", - "path": "/docs/installation/upgrading/upgrading-0.5-0.6.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.5-0.6.md" }, { "title": "v0.4 to v0.5", - "path": "/docs/installation/upgrading/upgrading-0.4-0.5.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.4-0.5.md" }, { "title": "v0.3 to v0.4", - "path": "/docs/installation/upgrading/upgrading-0.3-0.4.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.3-0.4.md" }, { "title": "v0.2 to v0.3", - "path": "/docs/installation/upgrading/upgrading-0.2-0.3.md" + "path": "/v1.12-docs/installation/upgrading/upgrading-0.2-0.3.md" } ] }, { "title": "Uninstall", - "path": "/docs/installation/uninstall.md" + "path": "/v1.12-docs/installation/uninstall.md" }, { "title": "API compatibility", - "path": "/docs/installation/api-compatibility.md" + "path": "/v1.12-docs/installation/api-compatibility.md" }, { "title": "Signature Verification", - "path": "/docs/installation/code-signing.md" + "path": "/v1.12-docs/installation/code-signing.md" }, { "title": "Best Practice", - "path": "/docs/installation/best-practice.md" + "path": "/v1.12-docs/installation/best-practice.md" } ] }, @@ -195,45 +195,45 @@ "routes": [ { "title": "Introduction", - "path": "/docs/configuration/README.md" + "path": "/v1.12-docs/configuration/README.md" }, { "title": "SelfSigned", - "path": "/docs/configuration/selfsigned.md" + "path": "/v1.12-docs/configuration/selfsigned.md" }, { "title": "CA", - "path": "/docs/configuration/ca.md" + "path": "/v1.12-docs/configuration/ca.md" }, { "title": "Vault", - "path": "/docs/configuration/vault.md" + "path": "/v1.12-docs/configuration/vault.md" }, { "title": "Venafi", - "path": "/docs/configuration/venafi.md" + "path": "/v1.12-docs/configuration/venafi.md" }, { "title": "External", - "path": "/docs/configuration/external.md" + "path": "/v1.12-docs/configuration/external.md" }, { "title": "ACME", "routes": [ { "title": "Introduction", - "path": "/docs/configuration/acme/README.md" + "path": "/v1.12-docs/configuration/acme/README.md" }, { "title": "HTTP01", "routes": [ { "title": "Introduction", - "path": "/docs/configuration/acme/http01/README.md" + "path": "/v1.12-docs/configuration/acme/http01/README.md" }, { "title": "External Load Balancer", - "path": "/docs/configuration/acme/http01/externalloadbalancer.md" + "path": "/v1.12-docs/configuration/acme/http01/externalloadbalancer.md" } ] }, @@ -242,43 +242,43 @@ "routes": [ { "title": "Introduction", - "path": "/docs/configuration/acme/dns01/README.md" + "path": "/v1.12-docs/configuration/acme/dns01/README.md" }, { "title": "ACMEDNS", - "path": "/docs/configuration/acme/dns01/acme-dns.md" + "path": "/v1.12-docs/configuration/acme/dns01/acme-dns.md" }, { "title": "Akamai", - "path": "/docs/configuration/acme/dns01/akamai.md" + "path": "/v1.12-docs/configuration/acme/dns01/akamai.md" }, { "title": "AzureDNS", - "path": "/docs/configuration/acme/dns01/azuredns.md" + "path": "/v1.12-docs/configuration/acme/dns01/azuredns.md" }, { "title": "Cloudflare", - "path": "/docs/configuration/acme/dns01/cloudflare.md" + "path": "/v1.12-docs/configuration/acme/dns01/cloudflare.md" }, { "title": "DigitalOcean", - "path": "/docs/configuration/acme/dns01/digitalocean.md" + "path": "/v1.12-docs/configuration/acme/dns01/digitalocean.md" }, { "title": "Google CloudDNS", - "path": "/docs/configuration/acme/dns01/google.md" + "path": "/v1.12-docs/configuration/acme/dns01/google.md" }, { "title": "RFC-2136", - "path": "/docs/configuration/acme/dns01/rfc2136.md" + "path": "/v1.12-docs/configuration/acme/dns01/rfc2136.md" }, { "title": "Route53", - "path": "/docs/configuration/acme/dns01/route53.md" + "path": "/v1.12-docs/configuration/acme/dns01/route53.md" }, { "title": "Webhook", - "path": "/docs/configuration/acme/dns01/webhook.md" + "path": "/v1.12-docs/configuration/acme/dns01/webhook.md" } ] } @@ -291,39 +291,39 @@ "routes": [ { "title": "Introduction", - "path": "/docs/usage/README.md" + "path": "/v1.12-docs/usage/README.md" }, { "title": "Certificate Resources", - "path": "/docs/usage/certificate.md" + "path": "/v1.12-docs/usage/certificate.md" }, { "title": "Prometheus Metrics", - "path": "/docs/usage/prometheus-metrics.md" + "path": "/v1.12-docs/usage/prometheus-metrics.md" }, { "title": "Securing Ingress Resources", - "path": "/docs/usage/ingress.md" + "path": "/v1.12-docs/usage/ingress.md" }, { "title": "Securing Gateway Resources", - "path": "/docs/usage/gateway.md" + "path": "/v1.12-docs/usage/gateway.md" }, { "title": "Securing Istio Service Mesh", - "path": "/docs/usage/istio.md" + "path": "/v1.12-docs/usage/istio.md" }, { "title": "CSI Driver", - "path": "/docs/usage/csi.md" + "path": "/v1.12-docs/usage/csi.md" }, { "title": "Kubernetes CertificateSigningRequests", - "path": "/docs/usage/kube-csr.md" + "path": "/v1.12-docs/usage/kube-csr.md" }, { "title": "Policy for cert-manager certificates", - "path": "/docs/usage/approver-policy.md" + "path": "/v1.12-docs/usage/approver-policy.md" } ] }, @@ -332,30 +332,30 @@ "routes": [ { "title": "Contents", - "path": "/docs/projects/README.md" + "path": "/v1.12-docs/projects/README.md" }, { "title": "istio-csr", - "path": "/docs/projects/istio-csr.md" + "path": "/v1.12-docs/projects/istio-csr.md" }, { "title": "csi-driver", - "path": "/docs/projects/csi-driver.md" + "path": "/v1.12-docs/projects/csi-driver.md" }, { "title": "csi-driver-spiffe", - "path": "/docs/projects/csi-driver-spiffe.md" + "path": "/v1.12-docs/projects/csi-driver-spiffe.md" }, { "title": "approver-policy", "routes": [ { "title": "Introduction", - "path": "/docs/projects/approver-policy/README.md" + "path": "/v1.12-docs/projects/approver-policy/README.md" }, { "title": "API Reference", - "path": "/docs/projects/approver-policy/api-reference.md" + "path": "/v1.12-docs/projects/approver-policy/api-reference.md" } ] }, @@ -364,11 +364,11 @@ "routes": [ { "title": "Introduction", - "path": "/docs/projects/trust-manager/README.md" + "path": "/v1.12-docs/projects/trust-manager/README.md" }, { "title": "API Reference", - "path": "/docs/projects/trust-manager/api-reference.md" + "path": "/v1.12-docs/projects/trust-manager/api-reference.md" } ] } @@ -379,55 +379,55 @@ "routes": [ { "title": "Introduction", - "path": "/docs/tutorials/README.md" + "path": "/v1.12-docs/tutorials/README.md" }, { "title": "Securing NGINX-ingress", - "path": "/docs/tutorials/acme/nginx-ingress.md" + "path": "/v1.12-docs/tutorials/acme/nginx-ingress.md" }, { "title": "GKE + Ingress + Let's Encrypt", - "path": "/docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md" + "path": "/v1.12-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md" }, { "title": "AKS + LoadBalancer + Let's Encrypt", - "path": "/docs/tutorials/getting-started-aks-letsencrypt/README.md" + "path": "/v1.12-docs/tutorials/getting-started-aks-letsencrypt/README.md" }, { "title": "Migrating from Kube-LEGO", - "path": "/docs/tutorials/acme/migrating-from-kube-lego.md" + "path": "/v1.12-docs/tutorials/acme/migrating-from-kube-lego.md" }, { "title": "Backup and Restore Resources", - "path": "/docs/tutorials/backup.md" + "path": "/v1.12-docs/tutorials/backup.md" }, { "title": "DNS Validation", - "path": "/docs/tutorials/acme/dns-validation.md" + "path": "/v1.12-docs/tutorials/acme/dns-validation.md" }, { "title": "HTTP Validation", - "path": "/docs/tutorials/acme/http-validation.md" + "path": "/v1.12-docs/tutorials/acme/http-validation.md" }, { "title": "Pomerium Ingress", - "path": "/docs/tutorials/acme/pomerium-ingress.md" + "path": "/v1.12-docs/tutorials/acme/pomerium-ingress.md" }, { "title": "EKS + Ingress + Venafi", - "path": "/docs/tutorials/venafi/venafi.md" + "path": "/v1.12-docs/tutorials/venafi/venafi.md" }, { "title": "Securing the istio Service Mesh using cert-manager", - "path": "/docs/tutorials/istio-csr/istio-csr.md" + "path": "/v1.12-docs/tutorials/istio-csr/istio-csr.md" }, { "title": "Syncing Secrets Across Namespaces", - "path": "/docs/tutorials/syncing-secrets-across-namespaces.md" + "path": "/v1.12-docs/tutorials/syncing-secrets-across-namespaces.md" }, { "title": "Securing Ingresses with ZeroSSL", - "path": "/docs/tutorials/zerossl/zerossl.md" + "path": "/v1.12-docs/tutorials/zerossl/zerossl.md" } ] }, @@ -436,90 +436,90 @@ "routes": [ { "title": "Introduction", - "path": "/docs/troubleshooting/README.md" + "path": "/v1.12-docs/troubleshooting/README.md" }, { "title": "Troubleshooting ACME / Let's Encrypt Certificates", - "path": "/docs/troubleshooting/acme.md" + "path": "/v1.12-docs/troubleshooting/acme.md" }, { "title": "Troubleshooting webhook", - "path": "/docs/troubleshooting/webhook.md" + "path": "/v1.12-docs/troubleshooting/webhook.md" } ] }, { "title": "FAQ", - "path": "/docs/faq/README.md" + "path": "/v1.12-docs/faq/README.md" }, { "title": "Contributing", "routes": [ { "title": "Introduction", - "path": "/docs/contributing/README.md" + "path": "/v1.12-docs/contributing/README.md" }, { "title": "Feature Policy", - "path": "/docs/contributing/policy.md" + "path": "/v1.12-docs/contributing/policy.md" }, { "title": "Building cert-manager", - "path": "/docs/contributing/building.md" + "path": "/v1.12-docs/contributing/building.md" }, { "title": "Contributing Flow", - "path": "/docs/contributing/contributing-flow.md" + "path": "/v1.12-docs/contributing/contributing-flow.md" }, { "title": "CRDs", - "path": "/docs/contributing/crds.md" + "path": "/v1.12-docs/contributing/crds.md" }, { "title": "DNS Providers", - "path": "/docs/contributing/dns-providers.md" + "path": "/v1.12-docs/contributing/dns-providers.md" }, { "title": "Running End-to-End Tests", - "path": "/docs/contributing/e2e.md" + "path": "/v1.12-docs/contributing/e2e.md" }, { "title": "Implementing External Issuers", - "path": "/docs/contributing/external-issuers.md" + "path": "/v1.12-docs/contributing/external-issuers.md" }, { "title": "DCO Sign Off", - "path": "/docs/contributing/sign-off.md" + "path": "/v1.12-docs/contributing/sign-off.md" }, { "title": "Release Process", - "path": "/docs/contributing/release-process.md" + "path": "/v1.12-docs/contributing/release-process.md" }, { "title": "Developing with Kind", - "path": "/docs/contributing/kind.md" + "path": "/v1.12-docs/contributing/kind.md" }, { "title": "Implementing Feature Gates", - "path": "/docs/contributing/featuregates.md" + "path": "/v1.12-docs/contributing/featuregates.md" }, { "title": "Google Season of Docs", "routes": [ { "title": "Introduction", - "path": "/docs/contributing/google-season-of-docs/README.md" + "path": "/v1.12-docs/contributing/google-season-of-docs/README.md" }, { "title": "2022", "routes": [ { "title": "Introduction", - "path": "/docs/contributing/google-season-of-docs/2022/README.md" + "path": "/v1.12-docs/contributing/google-season-of-docs/2022/README.md" }, { "title": "Improve the Navigation and Structure of the cert-manager Website", - "path": "/docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md" + "path": "/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md" } ] } @@ -527,23 +527,23 @@ }, { "title": "Reporting Security Issues", - "path": "/docs/contributing/security.md" + "path": "/v1.12-docs/contributing/security.md" }, { "title": "Coding Conventions", - "path": "/docs/contributing/coding-conventions.md" + "path": "/v1.12-docs/contributing/coding-conventions.md" }, { "title": "Third Party Code Donations", - "path": "/docs/contributing/third-party-code-donation.md" + "path": "/v1.12-docs/contributing/third-party-code-donation.md" }, { "title": "Signing Keys", - "path": "/docs/contributing/signing-keys.md" + "path": "/v1.12-docs/contributing/signing-keys.md" }, { "title": "Importing cert-manager in Go", - "path": "/docs/contributing/importing.md" + "path": "/v1.12-docs/contributing/importing.md" } ] }, @@ -552,119 +552,119 @@ "routes": [ { "title": "Introduction", - "path": "/docs/release-notes/README.md" + "path": "/v1.12-docs/release-notes/README.md" }, { "title": "v1.11", - "path": "/docs/release-notes/release-notes-1.11.md" + "path": "/v1.12-docs/release-notes/release-notes-1.11.md" }, { "title": "v1.10", - "path": "/docs/release-notes/release-notes-1.10.md" + "path": "/v1.12-docs/release-notes/release-notes-1.10.md" }, { "title": "v1.9", - "path": "/docs/release-notes/release-notes-1.9.md" + "path": "/v1.12-docs/release-notes/release-notes-1.9.md" }, { "title": "v1.8", - "path": "/docs/release-notes/release-notes-1.8.md" + "path": "/v1.12-docs/release-notes/release-notes-1.8.md" }, { "title": "v1.7", - "path": "/docs/release-notes/release-notes-1.7.md" + "path": "/v1.12-docs/release-notes/release-notes-1.7.md" }, { "title": "v1.6", - "path": "/docs/release-notes/release-notes-1.6.md" + "path": "/v1.12-docs/release-notes/release-notes-1.6.md" }, { "title": "v1.5", - "path": "/docs/release-notes/release-notes-1.5.md" + "path": "/v1.12-docs/release-notes/release-notes-1.5.md" }, { "title": "v1.4", - "path": "/docs/release-notes/release-notes-1.4.md" + "path": "/v1.12-docs/release-notes/release-notes-1.4.md" }, { "title": "v1.3", - "path": "/docs/release-notes/release-notes-1.3.md" + "path": "/v1.12-docs/release-notes/release-notes-1.3.md" }, { "title": "v1.2", - "path": "/docs/release-notes/release-notes-1.2.md" + "path": "/v1.12-docs/release-notes/release-notes-1.2.md" }, { "title": "v1.1", - "path": "/docs/release-notes/release-notes-1.1.md" + "path": "/v1.12-docs/release-notes/release-notes-1.1.md" }, { "title": "v1.0", - "path": "/docs/release-notes/release-notes-1.0.md" + "path": "/v1.12-docs/release-notes/release-notes-1.0.md" }, { "title": "v0.16", - "path": "/docs/release-notes/release-notes-0.16.md" + "path": "/v1.12-docs/release-notes/release-notes-0.16.md" }, { "title": "v0.15", - "path": "/docs/release-notes/release-notes-0.15.md" + "path": "/v1.12-docs/release-notes/release-notes-0.15.md" }, { "title": "v0.14", - "path": "/docs/release-notes/release-notes-0.14.md" + "path": "/v1.12-docs/release-notes/release-notes-0.14.md" }, { "title": "v0.13", - "path": "/docs/release-notes/release-notes-0.13.md" + "path": "/v1.12-docs/release-notes/release-notes-0.13.md" }, { "title": "v0.12", - "path": "/docs/release-notes/release-notes-0.12.md" + "path": "/v1.12-docs/release-notes/release-notes-0.12.md" }, { "title": "v0.11", - "path": "/docs/release-notes/release-notes-0.11.md" + "path": "/v1.12-docs/release-notes/release-notes-0.11.md" }, { "title": "v0.10", - "path": "/docs/release-notes/release-notes-0.10.md" + "path": "/v1.12-docs/release-notes/release-notes-0.10.md" }, { "title": "v0.9", - "path": "/docs/release-notes/release-notes-0.9.md" + "path": "/v1.12-docs/release-notes/release-notes-0.9.md" }, { "title": "v0.8", - "path": "/docs/release-notes/release-notes-0.8.md" + "path": "/v1.12-docs/release-notes/release-notes-0.8.md" }, { "title": "v0.7", - "path": "/docs/release-notes/release-notes-0.7.md" + "path": "/v1.12-docs/release-notes/release-notes-0.7.md" }, { "title": "v0.6", - "path": "/docs/release-notes/release-notes-0.6.md" + "path": "/v1.12-docs/release-notes/release-notes-0.6.md" }, { "title": "v0.5", - "path": "/docs/release-notes/release-notes-0.5.md" + "path": "/v1.12-docs/release-notes/release-notes-0.5.md" }, { "title": "v0.4", - "path": "/docs/release-notes/release-notes-0.4.md" + "path": "/v1.12-docs/release-notes/release-notes-0.4.md" }, { "title": "v0.3", - "path": "/docs/release-notes/release-notes-0.3.md" + "path": "/v1.12-docs/release-notes/release-notes-0.3.md" }, { "title": "v0.2", - "path": "/docs/release-notes/release-notes-0.2.md" + "path": "/v1.12-docs/release-notes/release-notes-0.2.md" }, { "title": "v0.1", - "path": "/docs/release-notes/release-notes-0.1.md" + "path": "/v1.12-docs/release-notes/release-notes-0.1.md" } ] }, @@ -673,31 +673,31 @@ "routes": [ { "title": "Introduction", - "path": "/docs/concepts/README.md" + "path": "/v1.12-docs/concepts/README.md" }, { "title": "Issuer", - "path": "/docs/concepts/issuer.md" + "path": "/v1.12-docs/concepts/issuer.md" }, { "title": "Certificate", - "path": "/docs/concepts/certificate.md" + "path": "/v1.12-docs/concepts/certificate.md" }, { "title": "CertificateRequest", - "path": "/docs/concepts/certificaterequest.md" + "path": "/v1.12-docs/concepts/certificaterequest.md" }, { "title": "ACME Orders and Challenges", - "path": "/docs/concepts/acme-orders-challenges.md" + "path": "/v1.12-docs/concepts/acme-orders-challenges.md" }, { "title": "Webhook", - "path": "/docs/concepts/webhook.md" + "path": "/v1.12-docs/concepts/webhook.md" }, { "title": "CA Injector", - "path": "/docs/concepts/ca-injector.md" + "path": "/v1.12-docs/concepts/ca-injector.md" } ] }, @@ -706,15 +706,15 @@ "routes": [ { "title": "Introduction", - "path": "/docs/reference/README.md" + "path": "/v1.12-docs/reference/README.md" }, { "title": "Command Line Tool (cmctl)", - "path": "/docs/reference/cmctl.md" + "path": "/v1.12-docs/reference/cmctl.md" }, { "title": "TLS Terminology", - "path": "/docs/reference/tls-terminology.md" + "path": "/v1.12-docs/reference/tls-terminology.md" }, { @@ -722,33 +722,33 @@ "routes": [ { "title": "Introduction", - "path": "/docs/cli/README.md" + "path": "/v1.12-docs/cli/README.md" }, { "title": "acmesolver", - "path": "/docs/cli/acmesolver.md" + "path": "/v1.12-docs/cli/acmesolver.md" }, { "title": "cainjector", - "path": "/docs/cli/cainjector.md" + "path": "/v1.12-docs/cli/cainjector.md" }, { "title": "cmctl", - "path": "/docs/cli/cmctl.md" + "path": "/v1.12-docs/cli/cmctl.md" }, { "title": "controller", - "path": "/docs/cli/controller.md" + "path": "/v1.12-docs/cli/controller.md" }, { "title": "webhook", - "path": "/docs/cli/webhook.md" + "path": "/v1.12-docs/cli/webhook.md" } ] }, { "title": "API Reference", - "path": "/docs/reference/api-docs.md" + "path": "/v1.12-docs/reference/api-docs.md" } ] } From f2e7ee91094fa9ff2c51163f8f1b5e0dce53856c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 20 Jun 2023 17:13:00 +0200 Subject: [PATCH 085/264] post-release 1.12: I forgot to edit the manifest.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I updated the release process to mention this. Command I ran is: jq 'del(.. | select(.path? | select(.) | test(".*(installation/supported-releases.md|installation/upgrading|release-notes).*")))' \ content/v1.12-docs/manifest.json >tmp && mv tmp content/v1.12-docs/manifest.json Signed-off-by: Maël Valais --- content/v1.12-docs/manifest.json | 244 +------------------------------ 1 file changed, 2 insertions(+), 242 deletions(-) diff --git a/content/v1.12-docs/manifest.json b/content/v1.12-docs/manifest.json index 39f9292d84..a8022ca65e 100644 --- a/content/v1.12-docs/manifest.json +++ b/content/v1.12-docs/manifest.json @@ -2,7 +2,6 @@ "routes": [ { "title": "cert-manager", - "routes": [ { "title": "Introduction", @@ -19,10 +18,6 @@ "title": "Introduction", "path": "/v1.12-docs/installation/README.md" }, - { - "title": "Supported Releases", - "path": "/v1.12-docs/installation/supported-releases.md" - }, { "title": "Cloud Compatibility", "path": "/v1.12-docs/installation/compatibility.md" @@ -53,124 +48,7 @@ }, { "title": "Upgrading", - "routes": [ - { - "title": "Introduction", - "path": "/v1.12-docs/installation/upgrading/README.md" - }, - { - "title": "Notes on Ingress Class Compatibility", - "path": "/v1.12-docs/installation/upgrading/ingress-class-compatibility.md" - }, - { - "title": "Migrating Deprecated API Resources", - "path": "/v1.12-docs/installation/upgrading/remove-deprecated-apis.md" - }, - { - "title": "v1.10 to v1.11", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.10-1.11.md" - }, - { - "title": "v1.9 to v1.10", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.9-1.10.md" - }, - { - "title": "v1.8 to v1.9", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.8-1.9.md" - }, - { - "title": "v1.7 to v1.8", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.7-1.8.md" - }, - { - "title": "v1.6 to v1.7", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.6-1.7.md" - }, - { - "title": "v1.5 to v1.6", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.5-1.6.md" - }, - { - "title": "v1.4 to v1.5", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.4-1.5.md" - }, - { - "title": "v1.3 to v1.4", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.3-1.4.md" - }, - { - "title": "v1.2 to v1.3", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.2-1.3.md" - }, - { - "title": "v1.1 to v1.2", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.1-1.2.md" - }, - { - "title": "v1.0 to v1.1", - "path": "/v1.12-docs/installation/upgrading/upgrading-1.0-1.1.md" - }, - { - "title": "v0.16 to v1.0", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.16-1.0.md" - }, - { - "title": "v0.15 to v0.16", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.15-0.16.md" - }, - { - "title": "v0.14 to v0.15", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.14-0.15.md" - }, - { - "title": "v0.13 to v0.14", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.13-0.14.md" - }, - { - "title": "v0.12 to v0.13", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.12-0.13.md" - }, - { - "title": "v0.11 to v0.12", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.11-0.12.md" - }, - { - "title": "v0.10 to v0.11", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.10-0.11.md" - }, - { - "title": "v0.9 to v0.10", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.9-0.10.md" - }, - { - "title": "v0.8 to v0.9", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.8-0.9.md" - }, - { - "title": "v0.7 to v0.8", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.7-0.8.md" - }, - { - "title": "v0.6 to v0.7", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.6-0.7.md" - }, - { - "title": "v0.5 to v0.6", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.5-0.6.md" - }, - { - "title": "v0.4 to v0.5", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.4-0.5.md" - }, - { - "title": "v0.3 to v0.4", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.3-0.4.md" - }, - { - "title": "v0.2 to v0.3", - "path": "/v1.12-docs/installation/upgrading/upgrading-0.2-0.3.md" - } - ] + "routes": [] }, { "title": "Uninstall", @@ -549,124 +427,7 @@ }, { "title": "Release Notes", - "routes": [ - { - "title": "Introduction", - "path": "/v1.12-docs/release-notes/README.md" - }, - { - "title": "v1.11", - "path": "/v1.12-docs/release-notes/release-notes-1.11.md" - }, - { - "title": "v1.10", - "path": "/v1.12-docs/release-notes/release-notes-1.10.md" - }, - { - "title": "v1.9", - "path": "/v1.12-docs/release-notes/release-notes-1.9.md" - }, - { - "title": "v1.8", - "path": "/v1.12-docs/release-notes/release-notes-1.8.md" - }, - { - "title": "v1.7", - "path": "/v1.12-docs/release-notes/release-notes-1.7.md" - }, - { - "title": "v1.6", - "path": "/v1.12-docs/release-notes/release-notes-1.6.md" - }, - { - "title": "v1.5", - "path": "/v1.12-docs/release-notes/release-notes-1.5.md" - }, - { - "title": "v1.4", - "path": "/v1.12-docs/release-notes/release-notes-1.4.md" - }, - { - "title": "v1.3", - "path": "/v1.12-docs/release-notes/release-notes-1.3.md" - }, - { - "title": "v1.2", - "path": "/v1.12-docs/release-notes/release-notes-1.2.md" - }, - { - "title": "v1.1", - "path": "/v1.12-docs/release-notes/release-notes-1.1.md" - }, - { - "title": "v1.0", - "path": "/v1.12-docs/release-notes/release-notes-1.0.md" - }, - { - "title": "v0.16", - "path": "/v1.12-docs/release-notes/release-notes-0.16.md" - }, - { - "title": "v0.15", - "path": "/v1.12-docs/release-notes/release-notes-0.15.md" - }, - { - "title": "v0.14", - "path": "/v1.12-docs/release-notes/release-notes-0.14.md" - }, - { - "title": "v0.13", - "path": "/v1.12-docs/release-notes/release-notes-0.13.md" - }, - { - "title": "v0.12", - "path": "/v1.12-docs/release-notes/release-notes-0.12.md" - }, - { - "title": "v0.11", - "path": "/v1.12-docs/release-notes/release-notes-0.11.md" - }, - { - "title": "v0.10", - "path": "/v1.12-docs/release-notes/release-notes-0.10.md" - }, - { - "title": "v0.9", - "path": "/v1.12-docs/release-notes/release-notes-0.9.md" - }, - { - "title": "v0.8", - "path": "/v1.12-docs/release-notes/release-notes-0.8.md" - }, - { - "title": "v0.7", - "path": "/v1.12-docs/release-notes/release-notes-0.7.md" - }, - { - "title": "v0.6", - "path": "/v1.12-docs/release-notes/release-notes-0.6.md" - }, - { - "title": "v0.5", - "path": "/v1.12-docs/release-notes/release-notes-0.5.md" - }, - { - "title": "v0.4", - "path": "/v1.12-docs/release-notes/release-notes-0.4.md" - }, - { - "title": "v0.3", - "path": "/v1.12-docs/release-notes/release-notes-0.3.md" - }, - { - "title": "v0.2", - "path": "/v1.12-docs/release-notes/release-notes-0.2.md" - }, - { - "title": "v0.1", - "path": "/v1.12-docs/release-notes/release-notes-0.1.md" - } - ] + "routes": [] }, { "title": "Concepts", @@ -716,7 +477,6 @@ "title": "TLS Terminology", "path": "/v1.12-docs/reference/tls-terminology.md" }, - { "title": "Components / Docker Images", "routes": [ From f1a7ed92f92c65d595f0ef98f84e088e5696ea31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 20 Jun 2023 17:22:56 +0200 Subject: [PATCH 086/264] release-process: explain how to properly update the manifest.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- .spelling | 1 + content/docs/contributing/release-process.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.spelling b/.spelling index 3580cb7ec8..3d0a6b35e2 100644 --- a/.spelling +++ b/.spelling @@ -255,6 +255,7 @@ VPC VaaS Velero Venafi +versioned WebhookConfiguration WIP YAML diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 162076c63e..0694ebd5b8 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -243,6 +243,8 @@ page if a step is missing or if it is outdated. cp -r content/docs content/v1.12-docs rm -rf content/v1.12-docs/{installation/supported-releases,installation/upgrading,release-notes} sed -i.bak 's|docs|v1.12-docs|g' content/v1.12-docs/manifest.json + jq 'del(.. | select(.path? | select(.) | test(".*(installation/supported-releases.md|installation/upgrading|release-notes).*")))' \ + content/v1.12-docs/manifest.json >tmp && mv tmp content/v1.12-docs/manifest.json ``` 6. (**final + patch releases**) Update the [API docs](https://cert-manager.io/docs/reference/api-docs/) and [CLI docs](https://cert-manager.io/docs/cli//): From b88c5262760af214c86b60921b624be8ec369abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 20 Jun 2023 18:21:10 +0200 Subject: [PATCH 087/264] release-process: RELASE_VERSION -> RELEASE_VERSION MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 0694ebd5b8..86665482b7 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -355,12 +355,12 @@ page if a step is missing or if it is outdated. 2. Add the tag for cmctl: - ```bash - # This tag is required to be able to go install cmctl - # See https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 - git tag -m"cmd/ctl/$RELEASE_VERSION" "cmd/ctl/$RELASE_VERSION" - git push origin "cmd/ctl/$RELEASE_VERSION" - ``` + ```bash + # This tag is required to be able to go install cmctl + # See https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 + git tag -m"cmd/ctl/$RELEASE_VERSION" "cmd/ctl/$RELEASE_VERSION" + git push origin "cmd/ctl/$RELEASE_VERSION" + ``` 9. Create the description for the GitHub Release: From 7cb717b7e74b2ad81742059b3738eb929757e99e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 20 Jun 2023 18:41:24 +0200 Subject: [PATCH 088/264] explain how to do the update to the go.mod file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 35 ++++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 86665482b7..e13d4ee39d 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -349,20 +349,35 @@ page if a step is missing or if it is outdated. kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). -8. Ensure that cmctl refers to the latest tag of cert-manager: +8. In this step, we make sure the Go module + `github.com/cert-manager/cert-manager/cmd/cmctl` can be imported by + third-parties. - 1. Bump cert-manager version in [cmctl `go.mod` file](https://github.com/cert-manager/cert-manager/blob/v1.12.0/cmd/ctl/go.mod#L15) and cherry-pick the commit to the release branch. + First, update the `cmd/cmctl`'s `go.mod` with the tag we just created: - 2. Add the tag for cmctl: + ```bash + # Must be run from the cert-manager repo folder. + cd cmd/cmctl + go get github.com/cert-manager/cert-manager@$RELEASE_VERSION + git add go.mod go.sum + git commit -m"Update cmd/cmctl's go.mod to $RELEASE_VERSION" + cd ../.. + ``` - ```bash - # This tag is required to be able to go install cmctl - # See https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 - git tag -m"cmd/ctl/$RELEASE_VERSION" "cmd/ctl/$RELEASE_VERSION" - git push origin "cmd/ctl/$RELEASE_VERSION" - ``` + Then, create a tag for the `cmd/cmctl` module: + + ```bash + # Must be run from the cert-manager repo folder. + git tag -m"cmd/ctl/$RELEASE_VERSION" "cmd/ctl/$RELEASE_VERSION" + git push origin "cmd/ctl/$RELEASE_VERSION" + ``` + + > **Note:** the reason we need to do this is explained on Stack Overflow: + [how-are-versions-of-a-sub-module-managed][] + + [how-are-versions-of-a-sub-module-managed]: https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 -9. Create the description for the GitHub Release: +9. In this section, we will be creating the description for the GitHub Release. > **Note:** This step is about creating the description that will be > copy-pasted into the GitHub release page. The creation of the "Release From ce38e4c58acab0856fc75871a86ca9cff0f5dc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 20 Jun 2023 19:12:47 +0200 Subject: [PATCH 089/264] release-process: let's set the env vars at the start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 161 +++++++++++-------- 1 file changed, 92 insertions(+), 69 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index e13d4ee39d..173b3010e6 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -128,8 +128,10 @@ some of these goals are missed, in order to keep up release velocity. page if a step is missing or if it is outdated.
      -1. Make sure to note which type of release you are doing. That will be helpful - in the next steps. +1. Remind yourself of our release terminology by looking at the following table. + This will allow you to know which steps to skip by looking the header of the + step, e.g., **(final release only)** means that this step must only be + performed when doing a final release. | Type of release | Example of git tag | |------------------------------------|--------------------| @@ -143,7 +145,44 @@ page if a step is missing or if it is outdated. [^1]: One or more "patch pre-releases" may be created to allow voluntary community testing of a bug fix or security fix before the fix is made generally available. The suffix `-beta` must be used for patch pre-releases. -2. **(final release only)** Prepare the Website "Upgrade Notes" PR. +2. Set the 4 env variables by copying the following snippet in your shell table: + + ```bash + export RELEASE_VERSION="v1.3.0-alpha.0" + export START_TAG="v1.2.0" + export END_REV="release-1.3" + export BRANCH="release-1.3" + ``` + + > **Note:** To help you fill in the correct values, use the following + > examples: + > + > | Variable | Example 1 | Example 2 | Example 2 | Example 3 | Example 4 | + > | ----------------- | ---------------- | ---------------- | ---------------- | ------------- | ------------- | + > | | initial alpha | subsequent alpha | beta release | final release | patch release | + > | `RELEASE_VERSION` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.3.0-beta.0` | `v1.3.0` | `v1.3.1` | + > | `START_TAG` | `v1.2.0` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.2.0`\* | `v1.3.0` | + > | `END_REV` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | + > | `BRANCH` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | + > + > \*Do not use a patch here (e.g., no `v1.2.3`). It must be `v1.2.0`: + > you must use the latest tag that belongs to the release branch you are + > releasing on; in the above example, the release branch is + > `release-1.3`, and the latest tag on that branch is `v1.2.0`. + + > **Note:** The 4 variables are described in [the README of the + `release-notes` + tool](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md#options). + For your convenience, the following table summarizes what you need to know: + > + > | Variable | Description | + > | ----------------- | --------------------------------------- | + > | `RELEASE_VERSION` | The git tag | + > | `START_TAG`\* | The git tag of the "previous"\* release | + > | `END_REV` | Name of your release branch (inclusive) | + > | `BRANCH` | Name of your release branch | + +3. **(final release only)** Prepare the Website "Upgrade Notes" PR. Make sure that a PR with the new upgrade document is ready to be merged on @@ -151,7 +190,7 @@ page if a step is missing or if it is outdated. example, see [upgrading-1.0-1.1](https://cert-manager.io/docs/installation/upgrading/upgrading-1.0-1.1/). -3. **(final + patch releases)** Prepare the Website "Release Notes" PR. +4. **(final + patch releases)** Prepare the Website "Release Notes" PR. **⚠️ This step can be done ahead of time.** @@ -199,7 +238,7 @@ page if a step is missing or if it is outdated. 8. Add a line to the file `content/docs/release-notes/README.md`. -4. **(final + patch release)** Prepare the Website "Release Notes" PR. +5. **(final + patch release)** Prepare the Website "Release Notes" PR. > ⚠️ This step can be done ahead of time. @@ -254,7 +293,7 @@ page if a step is missing or if it is outdated. ./scripts/gendocs/generate ``` -5. Check that the `origin` remote is correct. To do that, run the following +6. Check that the `origin` remote is correct. To do that, run the following command and make sure it returns the upstream `https://github.com/cert-manager/cert-manager.git`: @@ -270,7 +309,7 @@ page if a step is missing or if it is outdated. origin https://github.com/jetstack/cert-manager (push) ``` -6. Place yourself on the correct branch: +7. Place yourself on the correct branch: - **(initial alpha and subsequent alpha)**: place yourself on the `master` branch: @@ -330,30 +369,37 @@ page if a step is missing or if it is outdated. > This is only a temporary change to allow you to update the branch. > [Prow will re-apply the branch protection within 24 hours](https://docs.prow.k8s.io/docs/components/optional/branchprotector/#updating). -7. Create the required tags for the new release locally and push it upstream (starting the cert-manager build): +8. Create the required tags for the new release locally and push it upstream (starting the cert-manager build): ```bash - RELEASE_VERSION=v1.8.0-beta.0 + echo $RELEASE_VERSION git tag -m"$RELEASE_VERSION" $RELEASE_VERSION # be sure to push the named tag explicitly; you don't want to push any other local tags! git push origin $RELEASE_VERSION ``` - **GitHub permissions**: `git push` will only work if you have the - `admin` GitHub permission on the cert-manager repo to create or push to - the branch, see [prerequisites](#prerequisites). If you do not have this - permission, you will have to open a PR to merge master into the release - branch), and wait for the PR checks to become green. + > **Note**: `git push` will only work if you have the `admin` GitHub + > permission on the cert-manager repo to create or push to the branch, see + > [prerequisites](#prerequisites). If you do not have this permission, you + > will have to open a PR to merge master into the release branch), and + > wait for the PR checks to become green. - For recent versions of cert-manager, the tag being pushed will trigger a Google Cloud Build job to start, - kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to - the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). + > **Note 2:** For recent versions of cert-manager, the tag being pushed will trigger a Google Cloud Build job to start, + > kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to + > the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). -8. In this step, we make sure the Go module +1. In this step, we make sure the Go module `github.com/cert-manager/cert-manager/cmd/cmctl` can be imported by third-parties. - First, update the `cmd/cmctl`'s `go.mod` with the tag we just created: + First, create a temporary branch. + + ```bash + # Must be run from the cert-manager repo folder. + git checkout -b "update-cmd/ctl/$RELEASE_VERSION" + ``` + + Second, update the `cmd/cmctl`'s `go.mod` with the tag we just created: ```bash # Must be run from the cert-manager repo folder. @@ -364,7 +410,7 @@ page if a step is missing or if it is outdated. cd ../.. ``` - Then, create a tag for the `cmd/cmctl` module: + Third, create a tag for the `cmd/cmctl` module: ```bash # Must be run from the cert-manager repo folder. @@ -377,53 +423,30 @@ page if a step is missing or if it is outdated. [how-are-versions-of-a-sub-module-managed]: https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 -9. In this section, we will be creating the description for the GitHub Release. + Finally, open a PR to merge that change to master with the following + command: + + ```bash + gh pr create \ + --title "[Release $RELEASE_VERSION] Update cmd/cmctl's go.mod to $RELEASE_VERSION" \ + --body "Update cmd/cmctl's go.mod to $RELEASE_VERSION" \ + --base $BRANCH --head "update-cmd/ctl/$RELEASE_VERSION" + ``` + ``` + +2. In this section, we will be creating the description for the GitHub Release. > **Note:** This step is about creating the description that will be > copy-pasted into the GitHub release page. The creation of the "Release > Note" page on the website is done in a previous step. - 1. Use the following two tables to understand how to fill in the four - environment variables needed for the next step. These four environment - variables are documented on the - [README](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md#options) - for the Kubernetes `release-notes` tool. - - | Variable | Description | - | ----------------- | --------------------------------------- | - | `RELEASE_VERSION` | The git tag | - | `START_TAG`\* | The git tag of the "previous"\* release | - | `END_REV` | Name of your release branch (inclusive) | - | `BRANCH` | Name of your release branch | - - Examples for each release type (e.g., initial alpha release): - - | Variable | Example 1 | Example 2 | Example 2 | Example 3 | Example 4 | - | ----------------- | ---------------- | ---------------- | ---------------- | ------------- | ------------- | - | | | | | | | - | | initial alpha | subsequent alpha | beta release | final release | patch release | - | `RELEASE_VERSION` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.3.0-beta.0` | `v1.3.0` | `v1.3.1` | - | `START_TAG`\* | `v1.2.0` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.2.0`\*\* | `v1.3.0` | - | `END_REV` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | - | `BRANCH` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | - - > \*The git tag of the "previous" release (`START_TAG`) depends on which - > type of release you count on doing. Look at the above examples to - > understand a bit more what those are. - - > \*\*Do not use a patch here (e.g., no `v1.2.3`). It must be `v1.2.0`: - > you must use the latest tag that belongs to the release branch you are - > releasing on; in the above example, the release branch is - > `release-1.3`, and the latest tag on that branch is `v1.2.0`. - - After finding out the value for each of the 4 environment variables, set - the variables in your shell (for example, following the example 1): + 1. Check that all the env vars are ready: ```bash - export RELEASE_VERSION="v1.3.0-alpha.0" - export START_TAG="v1.2.0" - export END_REV="release-1.3" - export BRANCH="release-1.3" + echo $RELEASE_VERSION + echo $START_TAG + echo $END_REV + echo $BRANCH ``` 2. Generate `github-release-description.md` with the following command: @@ -448,7 +471,7 @@ page if a step is missing or if it is outdated. 4. **(final release only)** Write the section "Community" by taking example on past GitHub Releases. -10. Check that the build is complete and send Slack messages about the release: +3. Check that the build is complete and send Slack messages about the release: 1. For recent versions of cert-manager, the build will have been automatically triggered by the tag being pushed earlier. You can check if it's complete on @@ -487,7 +510,7 @@ page if a step is missing or if it is outdated. Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237

      -11. Run `cmrel publish`: +4. Run `cmrel publish`: 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are valid. Run the following command: @@ -532,7 +555,7 @@ page if a step is missing or if it is outdated. Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237

      -12. Publish the GitHub release: +5. Publish the GitHub release: 1. Visit the draft GitHub release and paste in the release notes that you generated earlier. You will need to manually edit the content to match @@ -547,7 +570,7 @@ page if a step is missing or if it is outdated. 4. Click "Publish" to make the GitHub release live. -13. Merge the pull request containing the Helm chart: +6. Merge the pull request containing the Helm chart: The Helm charts for cert-manager are served using Cloudflare pages and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). @@ -559,7 +582,7 @@ page if a step is missing or if it is outdated. 4. Merge the PR 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). -14. **(final + patch releases)** Merge the 4 Website PRs: +7. **(final + patch releases)** Merge the 4 Website PRs: 1. Merge the PRs "Release Notes", "Upgrade Notes", and "Freeze And Bump Versions" that you have created previously. @@ -577,7 +600,7 @@ page if a step is missing or if it is outdated. [ff-release-next]: https://github.com/cert-manager/website/compare/master...release-next?quick_pull=1&title=%5BPost-Release%5D+Merge+release-next+into+master&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco -15. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. +8. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. Assuming you have `brew` installed, you can use the `brew bump-formula-pr` command to do this. You'll need the new tag name and the commit hash of that @@ -594,7 +617,7 @@ page if a step is missing or if it is outdated. against https://github.com/homebrew/homebrew-core has been opened, continue with further release steps. -16. Post a Slack message as an answer to the first message. Toggle the check +9. Post a Slack message as an answer to the first message. Toggle the check box "Also send to `#cert-manager-dev`" so that the message is well visible. Also cross-post the message on `#cert-manager`. @@ -602,7 +625,7 @@ page if a step is missing or if it is outdated. https://github.com/cert-manager/cert-manager/releases/tag/v1.0.0 🎉

      -17. **(final release only)** Show the release to the world: +10. **(final release only)** Show the release to the world: 1. Send an email to [`cert-manager-dev@googlegroups.com`](https://groups.google.com/g/cert-manager-dev) @@ -615,7 +638,7 @@ page if a step is missing or if it is outdated. 3. Send a toot from the cert-manager Mastodon account! Login details are in Jetstack's 1password (for now). ([Example toot](https://infosec.exchange/@CertManager/109666434738850493)) -18. Proceed to the post-release "testing and release" steps: +11. Proceed to the post-release "testing and release" steps: 1. **(initial beta only)** Create a PR on [cert-manager/release](https://github.com/cert-manager/release) in order to From fabe0ed8be065deb2687595f9fd1fa634b145f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 20 Jun 2023 19:24:32 +0200 Subject: [PATCH 090/264] release-process: typos and fix ordered list numbering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 58 ++++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 173b3010e6..96359c287c 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -145,7 +145,8 @@ page if a step is missing or if it is outdated. [^1]: One or more "patch pre-releases" may be created to allow voluntary community testing of a bug fix or security fix before the fix is made generally available. The suffix `-beta` must be used for patch pre-releases. -2. Set the 4 env variables by copying the following snippet in your shell table: +2. Set the 4 environment variables by copying the following snippet in your + shell table: ```bash export RELEASE_VERSION="v1.3.0-alpha.0" @@ -388,7 +389,7 @@ page if a step is missing or if it is outdated. > kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to > the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). -1. In this step, we make sure the Go module +9. In this step, we make sure the Go module `github.com/cert-manager/cert-manager/cmd/cmctl` can be imported by third-parties. @@ -423,24 +424,23 @@ page if a step is missing or if it is outdated. [how-are-versions-of-a-sub-module-managed]: https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 - Finally, open a PR to merge that change to master with the following - command: + Finally, open a PR to merge that change and go back to the release branch + with the following commands: ```bash gh pr create \ --title "[Release $RELEASE_VERSION] Update cmd/cmctl's go.mod to $RELEASE_VERSION" \ - --body "Update cmd/cmctl's go.mod to $RELEASE_VERSION" \ - --base $BRANCH --head "update-cmd/ctl/$RELEASE_VERSION" - ``` + --body "Update cmd/cmctl's go.mod to $RELEASE_VERSION" --base $BRANCH + git checkout $BRANCH ``` -2. In this section, we will be creating the description for the GitHub Release. +10. In this section, we will be creating the description for the GitHub Release. > **Note:** This step is about creating the description that will be > copy-pasted into the GitHub release page. The creation of the "Release > Note" page on the website is done in a previous step. - 1. Check that all the env vars are ready: + 1. Check that all the 4 environment variables are ready: ```bash echo $RELEASE_VERSION @@ -471,7 +471,7 @@ page if a step is missing or if it is outdated. 4. **(final release only)** Write the section "Community" by taking example on past GitHub Releases. -3. Check that the build is complete and send Slack messages about the release: +11. Check that the build is complete and send Slack messages about the release: 1. For recent versions of cert-manager, the build will have been automatically triggered by the tag being pushed earlier. You can check if it's complete on @@ -510,7 +510,7 @@ page if a step is missing or if it is outdated. Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237

      -4. Run `cmrel publish`: +12. Run `cmrel publish`: 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are valid. Run the following command: @@ -537,17 +537,17 @@ page if a step is missing or if it is outdated. cmrel publish --nomock --release-name "$RELEASE_VERSION" ``` -
      - ⏰ Upon completion there will be: -
        -
      1. - A draft release of cert-manager on GitHub -
      2. -
      3. - A pull request containing the new Helm chart -
      4. -
      -
      +
      + ⏰ Upon completion there will be: +
        +
      1. + A draft release of cert-manager on GitHub +
      2. +
      3. + A pull request containing the new Helm chart +
      4. +
      +
      4. While the build is running, send a fourth Slack message in reply to the first message: @@ -555,7 +555,7 @@ page if a step is missing or if it is outdated. Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237

      -5. Publish the GitHub release: +13. Publish the GitHub release: 1. Visit the draft GitHub release and paste in the release notes that you generated earlier. You will need to manually edit the content to match @@ -570,7 +570,7 @@ page if a step is missing or if it is outdated. 4. Click "Publish" to make the GitHub release live. -6. Merge the pull request containing the Helm chart: +14. Merge the pull request containing the Helm chart: The Helm charts for cert-manager are served using Cloudflare pages and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). @@ -582,7 +582,7 @@ page if a step is missing or if it is outdated. 4. Merge the PR 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). -7. **(final + patch releases)** Merge the 4 Website PRs: +15. **(final + patch releases)** Merge the 4 Website PRs: 1. Merge the PRs "Release Notes", "Upgrade Notes", and "Freeze And Bump Versions" that you have created previously. @@ -600,7 +600,7 @@ page if a step is missing or if it is outdated. [ff-release-next]: https://github.com/cert-manager/website/compare/master...release-next?quick_pull=1&title=%5BPost-Release%5D+Merge+release-next+into+master&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco -8. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. +16. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. Assuming you have `brew` installed, you can use the `brew bump-formula-pr` command to do this. You'll need the new tag name and the commit hash of that @@ -617,7 +617,7 @@ page if a step is missing or if it is outdated. against https://github.com/homebrew/homebrew-core has been opened, continue with further release steps. -9. Post a Slack message as an answer to the first message. Toggle the check +17. Post a Slack message as an answer to the first message. Toggle the check box "Also send to `#cert-manager-dev`" so that the message is well visible. Also cross-post the message on `#cert-manager`. @@ -625,7 +625,7 @@ page if a step is missing or if it is outdated. https://github.com/cert-manager/cert-manager/releases/tag/v1.0.0 🎉

      -10. **(final release only)** Show the release to the world: +18. **(final release only)** Show the release to the world: 1. Send an email to [`cert-manager-dev@googlegroups.com`](https://groups.google.com/g/cert-manager-dev) @@ -638,7 +638,7 @@ page if a step is missing or if it is outdated. 3. Send a toot from the cert-manager Mastodon account! Login details are in Jetstack's 1password (for now). ([Example toot](https://infosec.exchange/@CertManager/109666434738850493)) -11. Proceed to the post-release "testing and release" steps: +19. Proceed to the post-release "testing and release" steps: 1. **(initial beta only)** Create a PR on [cert-manager/release](https://github.com/cert-manager/release) in order to From fe48763cf9aef4089a3147b7c7ade90da2276219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 20 Jun 2023 19:33:48 +0200 Subject: [PATCH 091/264] release-process: add a release-note block to the "gh pr create" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 28 +++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 96359c287c..06ddc0c7db 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -406,6 +406,7 @@ page if a step is missing or if it is outdated. # Must be run from the cert-manager repo folder. cd cmd/cmctl go get github.com/cert-manager/cert-manager@$RELEASE_VERSION + go mod tidy git add go.mod go.sum git commit -m"Update cmd/cmctl's go.mod to $RELEASE_VERSION" cd ../.. @@ -424,15 +425,28 @@ page if a step is missing or if it is outdated. [how-are-versions-of-a-sub-module-managed]: https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 - Finally, open a PR to merge that change and go back to the release branch + Then, open a PR to merge that change and go back to the release branch with the following commands: - ```bash - gh pr create \ - --title "[Release $RELEASE_VERSION] Update cmd/cmctl's go.mod to $RELEASE_VERSION" \ - --body "Update cmd/cmctl's go.mod to $RELEASE_VERSION" --base $BRANCH - git checkout $BRANCH - ``` + ```bash + gh pr create \ + --title "[Release $RELEASE_VERSION] Update cmd/cmctl's go.mod to $RELEASE_VERSION" \ + --body-file - --base $BRANCH < Date: Tue, 20 Jun 2023 20:08:41 +0200 Subject: [PATCH 092/264] release-process: tell ppl that we need to run "go mod tidy" everywhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 06ddc0c7db..58a3db63cd 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -406,10 +406,11 @@ page if a step is missing or if it is outdated. # Must be run from the cert-manager repo folder. cd cmd/cmctl go get github.com/cert-manager/cert-manager@$RELEASE_VERSION - go mod tidy - git add go.mod go.sum - git commit -m"Update cmd/cmctl's go.mod to $RELEASE_VERSION" cd ../.. + + find . -name go.mod -not -path ./_bin/\* -exec dirname '{}' \; | xargs -L1 -I@ sh -c 'cd @; go mod tidy' + git add **/go.mod **/go.sum + git commit -m"Update cmd/cmctl's go.mod to $RELEASE_VERSION" ``` Third, create a tag for the `cmd/cmctl` module: From 24493c431f3d1f446e072787b490e1342f660954 Mon Sep 17 00:00:00 2001 From: Erik Godding Boye Date: Tue, 20 Jun 2023 19:28:22 +0200 Subject: [PATCH 093/264] Add link to CEL approver-policy plugin Signed-off-by: Erik Godding Boye --- content/docs/projects/approver-policy/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/content/docs/projects/approver-policy/README.md b/content/docs/projects/approver-policy/README.md index 014e0930a0..bdad17d829 100644 --- a/content/docs/projects/approver-policy/README.md +++ b/content/docs/projects/approver-policy/README.md @@ -415,12 +415,17 @@ spec: val-1: key-1 ``` -There are currently no open source plugins. +## Known Plugins from the Community + +- [CEL approver-policy plugin](https://github.com/erikgb/cel-approver-policy-plugin) (experimental) If you want to implement an external approver policy plugin take a look at the example implementation at https://github.com/cert-manager/example-approver-policy-plugin. +Have you implemented a plugin for approver-policy? Feel free to add a link to your plugin from this page by +opening a pull request in the [cert-manager website project](https://github.com/cert-manager/website). + ## API Reference > 📖 Read the [approver-policy API reference](api-reference.md). From 67a501af054429babd5cc52324d6b4dbf21cb79f Mon Sep 17 00:00:00 2001 From: Peter Fiddes Date: Wed, 21 Jun 2023 09:51:56 +0100 Subject: [PATCH 094/264] docs: Correct trust-manager title Signed-off-by: Peter Fiddes --- content/docs/manifest.json | 2 +- .../tutorials/getting-started-with-trust-manager/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/manifest.json b/content/docs/manifest.json index fe17a201cd..61b90ca1b4 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -430,7 +430,7 @@ "path": "/docs/tutorials/zerossl/zerossl.md" }, { - "title": "Managing Public Trust in Kubernetes with Trust Manager", + "title": "Managing public trust in kubernetes with trust-manager", "path": "/docs/tutorials/getting-started-with-trust-manager/README.md" } ] diff --git a/content/docs/tutorials/getting-started-with-trust-manager/README.md b/content/docs/tutorials/getting-started-with-trust-manager/README.md index 999a84a141..5ce09cbc69 100644 --- a/content/docs/tutorials/getting-started-with-trust-manager/README.md +++ b/content/docs/tutorials/getting-started-with-trust-manager/README.md @@ -1,6 +1,6 @@ --- -title: Managing Public Trust in Kubernetes with Trust Manager -description: Learn how to deploy and configure trust-manager to automatically distribute your approved Public CA configuration to your entire Kubernetes cluster. +title: Managing public trust in kubernetes with trust-manager +description: Learn how to deploy and configure trust-manager to automatically distribute your approved Public CA configuration to your Kubernetes cluster. --- *Last Verified: 19 June 2023* From b4a18ec0e615e3d373ea05ead9e9b58221d5eb08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 23 Jun 2023 18:42:58 +0200 Subject: [PATCH 095/264] release-process: don't show a menu item for "Release Notes" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My previous script didn't remove the "routes" fields that had an empty array, which meant it was showing in the menu for no reason. Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 58a3db63cd..de57065254 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -283,8 +283,10 @@ page if a step is missing or if it is outdated. cp -r content/docs content/v1.12-docs rm -rf content/v1.12-docs/{installation/supported-releases,installation/upgrading,release-notes} sed -i.bak 's|docs|v1.12-docs|g' content/v1.12-docs/manifest.json - jq 'del(.. | select(.path? | select(.) | test(".*(installation/supported-releases.md|installation/upgrading|release-notes).*")))' \ - content/v1.12-docs/manifest.json >tmp && mv tmp content/v1.12-docs/manifest.json + cat content/v1.12-docs/manifest.json \ + | jq 'del(.. | select(.path? | select(.) | test(".*(installation/supported-releases.md|installation/upgrading|release-notes).*")))' \ + | jq 'del(.. | select(.routes? == []))' >/tmp/manifest \ + && mv /tmp/manifest content/v1.12-docs/manifest.json ``` 6. (**final + patch releases**) Update the [API docs](https://cert-manager.io/docs/reference/api-docs/) and [CLI docs](https://cert-manager.io/docs/cli//): From 932e47ba705d3b4f55fad087e999c2c2af705cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 23 Jun 2023 18:40:42 +0200 Subject: [PATCH 096/264] post-release 1.12: remove empty "Release Notes" in menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command used: cat content/v1.12-docs/manifest.json \ | jq 'del(.. | select(.path? | select(.) | test(".*(installation/supported-releases.md|installation/upgrading|release-notes).*")))' \ | jq 'del(.. | select(.routes? == []))' >/tmp/manifest \ && mv /tmp/manifest content/v1.12-docs/manifest.json Signed-off-by: Maël Valais --- content/v1.12-docs/manifest.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/content/v1.12-docs/manifest.json b/content/v1.12-docs/manifest.json index a8022ca65e..5bcb8ddf36 100644 --- a/content/v1.12-docs/manifest.json +++ b/content/v1.12-docs/manifest.json @@ -46,10 +46,6 @@ "title": "Feature flags", "path": "/v1.12-docs/installation/featureflags.md" }, - { - "title": "Upgrading", - "routes": [] - }, { "title": "Uninstall", "path": "/v1.12-docs/installation/uninstall.md" @@ -425,10 +421,6 @@ } ] }, - { - "title": "Release Notes", - "routes": [] - }, { "title": "Concepts", "routes": [ From 26068830a2081903faca6f0c0a29e0f61864eff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 23 Jun 2023 19:16:31 +0200 Subject: [PATCH 097/264] release-process: code review suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ashley Davis Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index de57065254..22cd6c6a1b 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -269,7 +269,7 @@ page if a step is missing or if it is outdated. rm -f **/*.bak ``` - To check that all mentions of that versions are gone, run: + To check that all mentions of that version are gone, run: ```bash grep -R -n -F 'v1.11.' content/docs/installation @@ -485,8 +485,7 @@ page if a step is missing or if it is outdated.

      3. Add a one-sentence summary at the top. - 4. **(final release only)** Write the section "Community" by taking example - on past GitHub Releases. + 4. **(final release only)** Write the "Community" section, following the example of past releases such as [v1.12.0](https://github.com/cert-manager/cert-manager/releases/tag/v1.12.0). If there are any users who didn't make code contributions but helped in other ways (testing, PR discussion, etc), be sure to thank them here! 11. Check that the build is complete and send Slack messages about the release: From c4e28410782056e5ec2462d0de2644bcfeec0373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 23 Jun 2023 19:18:40 +0200 Subject: [PATCH 098/264] post-release 1.12: add empty upgrade notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- .../docs/installation/upgrading/upgrading-1.11-1.12.md | 10 ++++++++++ content/docs/manifest.json | 4 ++++ 2 files changed, 14 insertions(+) create mode 100644 content/docs/installation/upgrading/upgrading-1.11-1.12.md diff --git a/content/docs/installation/upgrading/upgrading-1.11-1.12.md b/content/docs/installation/upgrading/upgrading-1.11-1.12.md new file mode 100644 index 0000000000..1ed3a68f72 --- /dev/null +++ b/content/docs/installation/upgrading/upgrading-1.11-1.12.md @@ -0,0 +1,10 @@ +--- +title: Upgrading from v1.11 to v1.12 +description: 'cert-manager installation: Upgrading v1.11 to v1.12' +--- + +There are no breaking changes between cert-manager 1.11 and 1.12. + +## Next Steps + +From here on you can follow the [regular upgrade process](./README.md). diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 61b90ca1b4..362e0f3d4d 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -66,6 +66,10 @@ "title": "Migrating Deprecated API Resources", "path": "/docs/installation/upgrading/remove-deprecated-apis.md" }, + { + "title": "v1.11 to v1.12", + "path": "/docs/installation/upgrading/upgrading-1.11-1.12.md" + }, { "title": "v1.10 to v1.11", "path": "/docs/installation/upgrading/upgrading-1.10-1.11.md" From 26ea4ef4ad5e83fc34e7824da4ab8b3c84992248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Sat, 24 Jun 2023 22:27:38 +0200 Subject: [PATCH 099/264] release-process: fix duplicate heading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 22cd6c6a1b..6b508477b8 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -239,9 +239,9 @@ page if a step is missing or if it is outdated. 8. Add a line to the file `content/docs/release-notes/README.md`. -5. **(final + patch release)** Prepare the Website "Release Notes" PR. +5. **(final + patch release)** Prepare the Website "Bump Versions" PR. - > ⚠️ This step can be done ahead of time. + **⚠️ This step can be done ahead of time.** In that PR: From 1ade58248166ca03970c9da589ec2bf16e612970 Mon Sep 17 00:00:00 2001 From: Rafael Tanaka Date: Wed, 26 Jul 2023 14:01:42 +0100 Subject: [PATCH 100/264] Update README.md Update sentence that was outdated. Signed-off-by: Rafael Tanaka --- content/v1.12-docs/projects/approver-policy/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/v1.12-docs/projects/approver-policy/README.md b/content/v1.12-docs/projects/approver-policy/README.md index aa7efcef6c..fa0c9d6b87 100644 --- a/content/v1.12-docs/projects/approver-policy/README.md +++ b/content/v1.12-docs/projects/approver-policy/README.md @@ -95,8 +95,8 @@ should be approved or denied. For a CertificateRequest to be appropriate for a policy and therefore be evaluated by it, it must be both bound via RBAC _and_ be selected by the policy -selector. CertificateRequestPolicy currently only supports `issuerRef` as a -selector. +selector. CertificateRequestPolicy currently supports `issuerRef` and `namespace` +as a selector. **If at least one policy permits the request, the request is approved. If at least one policy is appropriate for the request but none of those permit the From 85f146107a21c68e5caa31291bf412fcab1dba9f Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:22:56 +0200 Subject: [PATCH 101/264] add release notes for 1.12.1, 1.12.2 and 1.12.3 Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .../docs/release-notes/release-notes-1.12.md | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/content/docs/release-notes/release-notes-1.12.md b/content/docs/release-notes/release-notes-1.12.md index 4194faa683..1024ca2314 100644 --- a/content/docs/release-notes/release-notes-1.12.md +++ b/content/docs/release-notes/release-notes-1.12.md @@ -204,7 +204,51 @@ the Private CA Issuer. In addition, massive thanks to Jetstack (by Venafi) for contributing developer time and resources towards the continued maintenance of cert-manager projects. -## Changes since v1.11.0 +## `v1.12.3`: changes since `v1.12.2` + +### Changes by Kind +#### Bugfixes + +- BUGFIX: 1-character bug was causing invalid log messages and a memory leak ([#6235](https://github.com/cert-manager/cert-manager/pull/6235), @jetstack-bot) + +## `v1.12.2`: changes since `v1.12.1` + +### Known issues + +- cainjector contains a memory leak due to re-assignment of a log variable (see https://github.com/cert-manager/cert-manager/issues/6217). The fix will be released in v1.12.3. +See https://github.com/cert-manager/cert-manager/pull/6232 for context. + +### Changes by Kind + +#### Bugfixes + +- BUGFIX: `cmctl check api --wait 0` exited without output; we now make sure we perform the API check at least once (#6116, @jetstack-bot) + + +## `v1.12.1`: changes since `v1.12.0` + +This release contains a couple dependency bumps and changes to ACME external webhook library. + +### Known issues + +- [`cmctl` API check](https://cert-manager.io/docs/installation/verify/) is broken in v1.12.1. We suggest that you do not upgrade `cmctl` to this version. The fix will be released in v1.12.2. +See #6116 for context. +- cainjector contains a memory leak due to re-assignment of a log variable (see https://github.com/cert-manager/cert-manager/issues/6217). The fix will be released in v1.12.3. +See https://github.com/cert-manager/cert-manager/pull/6232 for context. + +### Changes by Kind + +#### Other (Cleanup or Flake) + +- Don't run API Priority and Fairness controller in webhook's extension apiserver ([#6085](https://github.com/cert-manager/cert-manager/pull/6085), [@irbekrm](https://github.com/irbekrm)) +- Adds a warning for folks to not use controller feature gates helm value to configure webhook feature gates ([#6100](https://github.com/cert-manager/cert-manager/pull/6100), [@irbekrm](https://github.com/irbekrm)) + +#### Uncategorized + +- Updates Kubernetes libraries to `v0.27.2`. ([#6077](https://github.com/cert-manager/cert-manager/pull/6077), [@lucacome](https://github.com/lucacome)) +- Updates controller-runtime to `v0.15.0` ([#6098](https://github.com/cert-manager/cert-manager/pull/6098), [@lucacome](https://github.com/lucacome)) + +## `v1.12.0`: changes since `v1.11.0` ### Feature From d47abc4182b3d2e91a4ccd6e6ddaebb0a7b53d55 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:43:48 +0200 Subject: [PATCH 102/264] fix spelling checker Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.spelling b/.spelling index e0a5a95e11..d46bce84d8 100644 --- a/.spelling +++ b/.spelling @@ -74,6 +74,7 @@ AzureDNS BKPR Bazel Bitnami +Bugfixes BundleSource BundleTarget BundleCondition @@ -488,6 +489,9 @@ v1.9.1 v1.10 v1.11.0 v1.12.0 +v1.12.1. +v1.12.2. +v1.12.3. v1alpha1 v1alpha2 v1alpha3 From 979c7c0e734e7c157f4cb410c2645b715c8832c8 Mon Sep 17 00:00:00 2001 From: cloudoutloud <39462069+cloudoutloud@users.noreply.github.com> Date: Sun, 6 Aug 2023 09:58:37 +0100 Subject: [PATCH 103/264] Update zeroSSL api version Signed-off-by: cloudoutloud <39462069+cloudoutloud@users.noreply.github.com> --- content/docs/tutorials/zerossl/zerossl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/tutorials/zerossl/zerossl.md b/content/docs/tutorials/zerossl/zerossl.md index e10d997ec9..dd3e6e1fbf 100644 --- a/content/docs/tutorials/zerossl/zerossl.md +++ b/content/docs/tutorials/zerossl/zerossl.md @@ -86,7 +86,7 @@ kubectl apply -f zero-ssl-eabsecret.yaml -n cert-manager Then we must create the `ZeroSSL` `ClusterIssuer`, let's call it `zerossl-production`. In our case we are using AWS. See pre-conditions to provision all required elements. ```yaml -apiVersion: cert-manager.io/v1alpha2 +apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: zerossl-production From 9a57d8b20d0c57a2fa3f2022d397bfc57db89c02 Mon Sep 17 00:00:00 2001 From: Swarup Ghosh Date: Mon, 7 Aug 2023 15:33:58 +0000 Subject: [PATCH 104/264] Add release notes for 1.11.2->1.11.4 Signed-off-by: Swarup Ghosh --- .spelling | 5 ++++ .../docs/release-notes/release-notes-1.11.md | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/.spelling b/.spelling index d46bce84d8..b9c342deae 100644 --- a/.spelling +++ b/.spelling @@ -487,7 +487,12 @@ v1.8.2 v1.9.0 v1.9.1 v1.10 +v1.11 v1.11.0 +v1.11.1 +v1.11.2 +v1.11.3 +v1.11.4 v1.12.0 v1.12.1. v1.12.2. diff --git a/content/docs/release-notes/release-notes-1.11.md b/content/docs/release-notes/release-notes-1.11.md index 648be9af6d..b608547a20 100644 --- a/content/docs/release-notes/release-notes-1.11.md +++ b/content/docs/release-notes/release-notes-1.11.md @@ -2,6 +2,34 @@ title: Release 1.11 description: 'cert-manager release notes: cert-manager 1.11' --- +## v1.11.4 + +### Other + +- Resolved docker/docker trivy CVE alert ([#6164](https://github.com/cert-manager/cert-manager/pull/6164), [@inteon](https://github.com/inteon)) +- Upgraded base images ([#6128](https://github.com/cert-manager/cert-manager/pull/6128), [@SgtCoDFish](https://github.com/SgtCoDFish)) + + +## v1.11.3 + +cert-manager `v1.11.3` mostly contains ACME library changes. API Priority and Fairness feature is now disabled in the external webhook's extension apiserver. + +### Other + +- API Priority and Fairness controller is now disabled in extension apiserver for DNS webhook implementation. ([#6092](https://github.com/cert-manager/cert-manager/pull/6092), [@irbekrm](https://github.com/irbekrm)) +- Adds a warning for folks to not use controller feature gates helm value to configure webhook feature gates ([#6101](https://github.com/cert-manager/cert-manager/pull/6101), [@irbekrm](https://github.com/irbekrm)) + +## v1.11.2 + +### Bug or Regression + +- Build with go 1.19.9 ([#6014](https://github.com/cert-manager/cert-manager/pull/6014), [@SgtCoDFish](https://github.com/SgtCoDFish)) + +### Other +- Bump the distroless base images ([#5930](https://github.com/cert-manager/cert-manager/pull/5930), [@maelvls](https://github.com/maelvls)) +- Bumps Docker libraries to fix vulnerability scan alert for `CVE-2023-28840`, `CVE-2023-28841`, `CVE-2023-28842` ([#6037](https://github.com/cert-manager/cert-manager/pull/6037), [@irbekrm](https://github.com/irbekrm)) - cert-manager was not actually affected by these CVEs which are all to do with Docker daemon's overlay network. +- Bumps Kubernetes libraries `v0.26.0` -> `v0.26.4` ([#6038](https://github.com/cert-manager/cert-manager/pull/6038), [@irbekrm](https://github.com/irbekrm)) - this might help with running cert-manager v1.11 on Kubernetes `v1.27` + ## v1.11.1 From 08da07dcc39d15174b87c786f703401b0dfbe57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Wed, 9 Aug 2023 14:04:12 +0200 Subject: [PATCH 105/264] Annual review of the OWNERS file (2023): remove Joakim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref: https://github.com/cert-manager/cert-manager/issues/6231 Signed-off-by: Maël Valais --- OWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/OWNERS b/OWNERS index f45d54a300..8d78d82c78 100644 --- a/OWNERS +++ b/OWNERS @@ -6,6 +6,5 @@ approvers: - irbekrm - maelvls - SgtCoDFish -- jahrlin - wallrj - inteon From 64d7c2415367b77097c8171ed7e3b6bbfe5721a1 Mon Sep 17 00:00:00 2001 From: John HU Date: Wed, 9 Aug 2023 10:26:50 -0700 Subject: [PATCH 106/264] Fix venafi link 404 Signed-off-by: John HU --- content/docs/configuration/venafi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/configuration/venafi.md b/content/docs/configuration/venafi.md index 0be6c09e3c..f6f692cb72 100644 --- a/content/docs/configuration/venafi.md +++ b/content/docs/configuration/venafi.md @@ -139,7 +139,7 @@ credentials. ### Access Token Authentication -1. [Set up token authentication](https://docs.venafi.com/Docs/23.1/TopNav/Content/SDK/AuthSDK/t-SDKa-Setup-OAuth.php). +1. [Set up token authentication](https://docs.venafi.com/Docs/23.1/TopNav/Content/SDK/AuthSDK/t-SDKa-Setup-OAuth.php). NOTE: Do not select "Refresh Token Enabled" and set a *long* "Token Validity (days)". @@ -147,7 +147,7 @@ credentials. E.g. `k8s-xyz-automation` -3. [Create a new application integration](https://docs.venafi.com/Docs/21.1/TopNav/Content/API-ApplicationIntegration/t-APIAppIntegrations-creatingNew-Aperture.php) +3. [Create a new application integration](https://docs.venafi.com/Docs/21.4/TopNav/Content/API-ApplicationIntegration/t-APIAppIntegrations-creatingNew-Aperture.php) Create an application integration with name and ID `cert-manager`. Set the "API Access Settings" to `Certificates: Read,Manage,Revoke`. From e4052dba2f2cbd2f468cd6e6171fedf73ea80541 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Wed, 16 Aug 2023 12:50:23 +0100 Subject: [PATCH 107/264] add docs on trust namespace Signed-off-by: Ashley Davis --- content/docs/projects/trust-manager/README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/content/docs/projects/trust-manager/README.md b/content/docs/projects/trust-manager/README.md index 5882feb966..bbb2d318fa 100644 --- a/content/docs/projects/trust-manager/README.md +++ b/content/docs/projects/trust-manager/README.md @@ -49,12 +49,12 @@ spec: # those issued by Let's Encrypt, Google, Amazon and others. - useDefaultCAs: true - # A Secret in the trust-manager namespace + # A Secret in the "trust" namespace; see "Trust Namespace" below for further details - secret: name: "my-db-tls" key: "ca.crt" - # A ConfigMap in the trust-manager namespace + # A ConfigMap in the "trust" namespace; see "Trust Namespace" below for further details - configMap: name: "my-org.net" key: "root-certs.pem" @@ -138,6 +138,21 @@ We strongly recommend that you install trust-manager using Helm and we don't cur versions of trust-manager. This is so that we can focus on continuing to improve trust-manager with the resources we currently have available. +### Trust Namespace + +One of the more important configuration options you might need to consider at install time is which "trust namespace" to use, +which can be set via the Helm value `app.trust.namespace`. + +The trust namespace is the only one in which `Secret` and `ConfigMap` sources can be read. This restriction is in place +for security reasons - we don't want to give trust-manager the permission to read all `Secret`s or `ConfigMap`s in all namespaces. + +The trust namespace defaults to `cert-manager`, but there's no need for it to be set to the namespace that cert-manager +is installed in - trust-manager has no runtime dependency on cert-manager at all! - so we'd recommend setting the trust +namespace to whichever is most appropriate for your environment. + +An ideal deployment would be a fresh namespace dedicated entirely to trust-manager, to minimize the number of actors in your +cluster that can modify your trust sources. + ## Quick Start Example Let's get started with an example of creating our own `Bundle`! From 38dc79a54bf1fb73adbc3789e5dfa7139a38b2aa Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Fri, 18 Aug 2023 16:50:40 +0100 Subject: [PATCH 108/264] update supported releases about 1.13 Signed-off-by: Ashley Davis --- content/docs/installation/supported-releases.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 160ec17fc4..841c38b8ee 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -17,14 +17,16 @@ and other world events. | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| -| [1.12][] | May 19, 2023 | End of September, 2023 | 1.22 → 1.27 | 4.9 → 4.14 | +| [1.12][] | May 19, 2023 | Release of 1.14 | 1.22 → 1.27 | 4.9 → 4.14 | | [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.27 | 4.8 → 4.14 | ## Upcoming releases | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:----------------------:|:----------------------:|:----------------------------------:|:---------------------------------:| -| [1.13][] | End of July, 2023 | End of November, 2023 | 1.22 → 1.27 | 4.9 → 4.14 | +| [1.13][] | TBD | ~4 months post release | 1.22 → 1.27 | 4.9 → 4.14 | + +The release of cert-manager 1.13 has been delayed while we work to ensure it's the best it can possibly be. We'll have more updates soon! Dates in the future are uncertain and might change. From d84b0d70eb3b1fcab8687b231f27f39ecbeb6b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 18 Aug 2023 18:19:44 +0200 Subject: [PATCH 109/264] Annual review of the OWNERS file (2023): Maartje moved to Emeritus Maintainer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref: https://github.com/cert-manager/cert-manager/issues/6231 Signed-off-by: Maël Valais --- OWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/OWNERS b/OWNERS index 8d78d82c78..36d1cae66f 100644 --- a/OWNERS +++ b/OWNERS @@ -1,7 +1,6 @@ approvers: - munnerz - JoshVanL -- meyskens - jakexks - irbekrm - maelvls From a91777d44a94ca34ce9b28d31c889c2069b0868a Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Thu, 24 Aug 2023 16:27:15 +0100 Subject: [PATCH 110/264] add docs for trust-manager v0.6.0 Signed-off-by: Ashley Davis --- .spelling | 2 ++ content/docs/projects/trust-manager/README.md | 14 ++++++++++++++ .../docs/projects/trust-manager/api-reference.md | 8 ++++---- scripts/gendocs/generate-trust-manager | 2 +- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.spelling b/.spelling index b9c342deae..a8e5948b8b 100644 --- a/.spelling +++ b/.spelling @@ -622,6 +622,8 @@ ssl cert.pem Rollout rollout +JKS-formatted +changeit # TEMPORARY # these are temporarily ignored because the spellchecker diff --git a/content/docs/projects/trust-manager/README.md b/content/docs/projects/trust-manager/README.md index bbb2d318fa..7507585c4b 100644 --- a/content/docs/projects/trust-manager/README.md +++ b/content/docs/projects/trust-manager/README.md @@ -132,6 +132,20 @@ helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set install helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --wait ``` +### approver-policy Integration + +If you're running [approver-policy](../approver-policy/README.md) then cert-manager's default approver will be disabled which will mean that +trust-manager's webhook certificate will - by default - block when you install the Helm chart until it's manually approved. + +As of trust-manager v0.6.0 you can choose to automatically add an approver-policy `CertificateRequestPolicy` which +will approve the trust-manager webhook certificate: + +```bash +helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --set app.webhook.tls.approverPolicy.enabled=true --set app.webhook.tls.approverPolicy.certManagerNamespace=cert-manager --wait +``` + +Note that if you've installed cert-manager to a different namespace, you'll need to pass that namespace in `app.webhook.tls.approverPolicy.certManagerNamespace`! + ### Manual Installation We strongly recommend that you install trust-manager using Helm and we don't currently support manually installed diff --git a/content/docs/projects/trust-manager/api-reference.md b/content/docs/projects/trust-manager/api-reference.md index 44ea6f6d18..da09c7c039 100644 --- a/content/docs/projects/trust-manager/api-reference.md +++ b/content/docs/projects/trust-manager/api-reference.md @@ -268,7 +268,7 @@ AdditionalFormats specifies any additional formats to write to the target jks object - KeySelector is a reference to a key for some map data object.
      + JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit".
      false @@ -278,7 +278,7 @@ AdditionalFormats specifies any additional formats to write to the target ### `Bundle.spec.target.additionalFormats.jks` -KeySelector is a reference to a key for some map data object. +JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit". @@ -510,7 +510,7 @@ AdditionalFormats specifies any additional formats to write to the target @@ -520,7 +520,7 @@ AdditionalFormats specifies any additional formats to write to the target ### `Bundle.status.target.additionalFormats.jks` -KeySelector is a reference to a key for some map data object. +JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit".
      jks object - KeySelector is a reference to a key for some map data object.
      + JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit".
      false
      diff --git a/scripts/gendocs/generate-trust-manager b/scripts/gendocs/generate-trust-manager index b7168bcc96..3d6a6d6dfb 100755 --- a/scripts/gendocs/generate-trust-manager +++ b/scripts/gendocs/generate-trust-manager @@ -59,6 +59,6 @@ gendocs() { echo "+++ Cloning trust-manager repository..." git clone "https://github.com/cert-manager/trust-manager.git" "$tmpdir" -checkout "v0.5.0" +checkout "v0.6.0" gendocs "$REPO_ROOT/content/docs/projects/trust-manager/api-reference.md" From ecda51b6b6c7a0ee16d724498b1d7b57bd2b740c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 16:48:44 +0200 Subject: [PATCH 111/264] renaming all references to jetstack/testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ref: https://github.com/cert-manager/testing/issues/904 Signed-off-by: Maël Valais --- README.md | 2 +- content/docs/contributing/e2e.md | 2 +- content/docs/contributing/release-process.md | 14 +++++++------- content/v1.12-docs/contributing/e2e.md | 2 +- content/v1.12-docs/contributing/release-process.md | 14 +++++++------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6601f58100..449d765862 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ This will automatically run a number of checks against your local environment, i > ℹ️ All these checks are also run automatically for pull requests. > The results will be reported in the [checks summary](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks) at the bottom of your GitHub PR. -> Read the [cert-manager-website-presubmits.yaml prow configuration file](https://github.com/jetstack/testing/blob/master/config/jobs/cert-manager/website/cert-manager-website-presubmits.yaml) and the [check.yaml workflow file](.github/workflows/check.yaml) for more details. +> Read the [cert-manager-website-presubmits.yaml prow configuration file](https://github.com/cert-manager/testing/blob/master/config/jobs/cert-manager/website/cert-manager-website-presubmits.yaml) and the [check.yaml workflow file](.github/workflows/check.yaml) for more details. ### Building for a Release diff --git a/content/docs/contributing/e2e.md b/content/docs/contributing/e2e.md index 5037b28fa4..9e6b9e7409 100644 --- a/content/docs/contributing/e2e.md +++ b/content/docs/contributing/e2e.md @@ -137,7 +137,7 @@ The master branch of cert-manager can also be tested against different cloud pro The infrastructure used to run the e2e tests on cloud providers is present in the [cert-manager/test-infra](https://github.com/cert-manager/test-infra) repository. More cloud providers can be added by creating infrastructure for them using [Terraform](https://www.terraform.io/). -Apart from that, tests for the existing infrastructure can be customized by editing their respective prow jobs present in the [Jetstack testing repository](https://github.com/jetstack/testing/tree/master/config/jobs/cert-manager) repository. Values like the cert-manager version or the cloud provider version are present as variables in Terraform so their values can be changed when using `terraform apply` in the prow jobs, for example, for the [EKS prow job](https://github.com/jetstack/testing/blob/master/config/jobs/cert-manager/cert-manager-periodics.yaml#L524) the cert-manager version being tested can be changed using +Apart from that, tests for the existing infrastructure can be customized by editing their respective prow jobs present in the [Jetstack testing repository](https://github.com/cert-manager/testing/tree/master/config/jobs/cert-manager) repository. Values like the cert-manager version or the cloud provider version are present as variables in Terraform so their values can be changed when using `terraform apply` in the prow jobs, for example, for the [EKS prow job](https://github.com/cert-manager/testing/blob/master/config/jobs/cert-manager/cert-manager-periodics.yaml#L524) the cert-manager version being tested can be changed using ```console terraform apply -var="cert_manager_version=v1.3.3" -auto-approve diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 6b508477b8..30d273485a 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -365,7 +365,7 @@ page if a step is missing or if it is outdated. We don't fast-forward for patch releases and final releases; instead, we prepare these releases using the `/cherry-pick release-1.0` command. - > Note about branch protection: The release branches are protected by [GitHub branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule), which is [configured automatically by Prow](https://github.com/jetstack/testing/blob/500b990ad1278982b10d57bf8fbca383040d2fe8/config/config.yaml#L27-L36). + > Note about branch protection: The release branches are protected by [GitHub branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule), which is [configured automatically by Prow](https://github.com/cert-manager/testing/blob/500b990ad1278982b10d57bf8fbca383040d2fe8/config/config.yaml#L27-L36). > This prevents anyone *accidentally* pushing changes directly to these branches, even repository administrators. > If you need, for some reason, to fast forward the release branch, > you should delete the branch protection for that release branch, using the [GitHub branch protection web UI](https://github.com/cert-manager/cert-manager/settings/branches). @@ -658,11 +658,11 @@ page if a step is missing or if it is outdated. 1. **(initial beta only)** Create a PR on [cert-manager/release](https://github.com/cert-manager/release) in order to - add the new release to our list of periodic ProwJobs. Use [this PR](https://github.com/jetstack/testing/pull/774/) as an example. + add the new release to our list of periodic ProwJobs. Use [this PR](https://github.com/cert-manager/testing/pull/774/) as an example. 2. **(initial beta only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and - open a PR to [cert-manager/testing](https://github.com/jetstack/testing) adding the generated prow configs. - Use [this PR](https://github.com/jetstack/testing/pull/766) as an example. + open a PR to [cert-manager/testing](https://github.com/cert-manager/testing) adding the generated prow configs. + Use [this PR](https://github.com/cert-manager/testing/pull/766) as an example. 3. **(final release only)** Create a PR on [cert-manager/release](https://github.com/cert-manager/release), @@ -675,10 +675,10 @@ page if a step is missing or if it is outdated. This will remove the periodic ProwJobs for this version as they're no longer needed. 4. **(final release only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and - open a PR to [jetstack/testing](https://github.com/jetstack/testing) adding the generated prow configs. + open a PR to [jetstack/testing](https://github.com/cert-manager/testing) adding the generated prow configs. - 5. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/jetstack/testing) - and update the [milestone_applier](https://github.com/jetstack/testing/blob/3110b68e082c3625bf0d26265be2d29e41da14b2/config/plugins.yaml#L69) + 5. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/cert-manager/testing) + and update the [milestone_applier](https://github.com/cert-manager/testing/blob/3110b68e082c3625bf0d26265be2d29e41da14b2/config/plugins.yaml#L69) config so that newly raised PRs on master are applied to a new milestone for the next release. E.g. if master currently points at the `v1.10` milestone, change it to point at `v1.11`. diff --git a/content/v1.12-docs/contributing/e2e.md b/content/v1.12-docs/contributing/e2e.md index 5037b28fa4..9e6b9e7409 100644 --- a/content/v1.12-docs/contributing/e2e.md +++ b/content/v1.12-docs/contributing/e2e.md @@ -137,7 +137,7 @@ The master branch of cert-manager can also be tested against different cloud pro The infrastructure used to run the e2e tests on cloud providers is present in the [cert-manager/test-infra](https://github.com/cert-manager/test-infra) repository. More cloud providers can be added by creating infrastructure for them using [Terraform](https://www.terraform.io/). -Apart from that, tests for the existing infrastructure can be customized by editing their respective prow jobs present in the [Jetstack testing repository](https://github.com/jetstack/testing/tree/master/config/jobs/cert-manager) repository. Values like the cert-manager version or the cloud provider version are present as variables in Terraform so their values can be changed when using `terraform apply` in the prow jobs, for example, for the [EKS prow job](https://github.com/jetstack/testing/blob/master/config/jobs/cert-manager/cert-manager-periodics.yaml#L524) the cert-manager version being tested can be changed using +Apart from that, tests for the existing infrastructure can be customized by editing their respective prow jobs present in the [Jetstack testing repository](https://github.com/cert-manager/testing/tree/master/config/jobs/cert-manager) repository. Values like the cert-manager version or the cloud provider version are present as variables in Terraform so their values can be changed when using `terraform apply` in the prow jobs, for example, for the [EKS prow job](https://github.com/cert-manager/testing/blob/master/config/jobs/cert-manager/cert-manager-periodics.yaml#L524) the cert-manager version being tested can be changed using ```console terraform apply -var="cert_manager_version=v1.3.3" -auto-approve diff --git a/content/v1.12-docs/contributing/release-process.md b/content/v1.12-docs/contributing/release-process.md index 4e0b074fcf..82d2318899 100644 --- a/content/v1.12-docs/contributing/release-process.md +++ b/content/v1.12-docs/contributing/release-process.md @@ -218,7 +218,7 @@ page if a step is missing or if it is outdated. We don't fast-forward for patch releases and final releases; instead, we prepare these releases using the `/cherry-pick release-1.0` command. - > Note about branch protection: The release branches are protected by [GitHub branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule), which is [configured automatically by Prow](https://github.com/jetstack/testing/blob/500b990ad1278982b10d57bf8fbca383040d2fe8/config/config.yaml#L27-L36). + > Note about branch protection: The release branches are protected by [GitHub branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule), which is [configured automatically by Prow](https://github.com/cert-manager/testing/blob/500b990ad1278982b10d57bf8fbca383040d2fe8/config/config.yaml#L27-L36). > This prevents anyone *accidentally* pushing changes directly to these branches, even repository administrators. > If you need, for some reason, to fast forward the release branch, > you should delete the branch protection for that release branch, using the [GitHub branch protection web UI](https://github.com/cert-manager/cert-manager/settings/branches). @@ -493,11 +493,11 @@ page if a step is missing or if it is outdated. 1. **(initial beta only)** Create a PR on [cert-manager/release](https://github.com/cert-manager/release) in order to - add the new release to our list of periodic ProwJobs. Use [this PR](https://github.com/jetstack/testing/pull/774/) as an example. + add the new release to our list of periodic ProwJobs. Use [this PR](https://github.com/cert-manager/testing/pull/774/) as an example. 2. **(initial beta only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and - open a PR to [cert-manager/testing](https://github.com/jetstack/testing) adding the generated prow configs. - Use [this PR](https://github.com/jetstack/testing/pull/766) as an example. + open a PR to [cert-manager/testing](https://github.com/cert-manager/testing) adding the generated prow configs. + Use [this PR](https://github.com/cert-manager/testing/pull/766) as an example. 3. If needed, open a PR to [`cert-manager/website`](https://github.com/cert-manager/website) in @@ -518,10 +518,10 @@ page if a step is missing or if it is outdated. This will remove the periodic ProwJobs for this version as they're no longer needed. 5. **(final release only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and - open a PR to [jetstack/testing](https://github.com/jetstack/testing) adding the generated prow configs. + open a PR to [jetstack/testing](https://github.com/cert-manager/testing) adding the generated prow configs. - 6. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/jetstack/testing) - and update the [milestone_applier](https://github.com/jetstack/testing/blob/3110b68e082c3625bf0d26265be2d29e41da14b2/config/plugins.yaml#L69) + 6. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/cert-manager/testing) + and update the [milestone_applier](https://github.com/cert-manager/testing/blob/3110b68e082c3625bf0d26265be2d29e41da14b2/config/plugins.yaml#L69) config so that newly raised PRs on master are applied to a new milestone for the next release. E.g. if master currently points at the `v1.10` milestone, change it to point at `v1.11`. From 8860776216e3158801f6b263a8bea4a7fcf82a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 16:52:21 +0200 Subject: [PATCH 112/264] 1.11.5 and 1.12.4: release notes updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- .spelling | 7 + content/docs/reference/api-docs.md | 2 +- .../docs/release-notes/release-notes-1.11.md | 6 +- .../docs/release-notes/release-notes-1.12.md | 139 ++++++++++-------- content/v1.12-docs/reference/api-docs.md | 2 +- 5 files changed, 93 insertions(+), 63 deletions(-) diff --git a/.spelling b/.spelling index a8e5948b8b..c37419c58b 100644 --- a/.spelling +++ b/.spelling @@ -12,6 +12,7 @@ mmontes11 lvyanru8200 vedauth CVE-2022-41717 +CVE-2023-29409 seccomp RedHat RoleBinding @@ -75,6 +76,7 @@ BKPR Bazel Bitnami Bugfixes +bugfix BundleSource BundleTarget BundleCondition @@ -456,6 +458,10 @@ v0.16 v0.23.1 v1 v1.12 +v1.12.1 +v1.12.2 +v1.12.3 +v1.12.4 v1.13 v1.16 v1.19 @@ -493,6 +499,7 @@ v1.11.1 v1.11.2 v1.11.3 v1.11.4 +v1.11.5 v1.12.0 v1.12.1. v1.12.2. diff --git a/content/docs/reference/api-docs.md b/content/docs/reference/api-docs.md index f098ef7b0b..e766b7cfeb 100644 --- a/content/docs/reference/api-docs.md +++ b/content/docs/reference/api-docs.md @@ -5760,5 +5760,5 @@ description: >-

      - Generated with gen-crd-api-reference-docs on git commit ca9aaa0. + Generated with gen-crd-api-reference-docs on git commit fe41951.

      diff --git a/content/docs/release-notes/release-notes-1.11.md b/content/docs/release-notes/release-notes-1.11.md index b608547a20..b1e6a46a41 100644 --- a/content/docs/release-notes/release-notes-1.11.md +++ b/content/docs/release-notes/release-notes-1.11.md @@ -2,6 +2,11 @@ title: Release 1.11 description: 'cert-manager release notes: cert-manager 1.11' --- + +## v1.11.5 + +- Use Go 1.19.9 to fix a security issue in Go's `crypto/tls` library. ([#6317](https://github.com/cert-manager/cert-manager/pull/6317), [@maelvls](https://github.com/maelvls)) + ## v1.11.4 ### Other @@ -9,7 +14,6 @@ description: 'cert-manager release notes: cert-manager 1.11' - Resolved docker/docker trivy CVE alert ([#6164](https://github.com/cert-manager/cert-manager/pull/6164), [@inteon](https://github.com/inteon)) - Upgraded base images ([#6128](https://github.com/cert-manager/cert-manager/pull/6128), [@SgtCoDFish](https://github.com/SgtCoDFish)) - ## v1.11.3 cert-manager `v1.11.3` mostly contains ACME library changes. API Priority and Fairness feature is now disabled in the external webhook's extension apiserver. diff --git a/content/docs/release-notes/release-notes-1.12.md b/content/docs/release-notes/release-notes-1.12.md index 1024ca2314..3f964d4bf3 100644 --- a/content/docs/release-notes/release-notes-1.12.md +++ b/content/docs/release-notes/release-notes-1.12.md @@ -3,14 +3,77 @@ title: Release 1.12 description: 'cert-manager release notes: cert-manager 1.12' --- +## v1.12.4 + +v1.12.4 contains an important security fix that +addresses [CVE-2023-29409](https://cve.report/CVE-2023-29409). + +### Changes + +- Fixes an issue where cert-manager would incorrectly reject two IP addresses as + being unequal when they should have compared equal. This would be most + noticeable when using an IPv6 address which doesn't match how Go's + `net.IP.String()` function would have printed that address. + ([#6297](https://github.com/cert-manager/cert-manager/pull/6297), + [@SgtCoDFish](https://github.com/SgtCoDFish)) +- Use Go 1.20.7 to fix a security issue in Go's `crypto/tls` library. + ([#6318](https://github.com/cert-manager/cert-manager/pull/6318), + [@maelvls](https://github.com/maelvls)) + +## v1.12.3 + +v1.12.3 contains a bug fix for the cainjector which addresses a memory leak! + +### Changes + +- BUGFIX\[cainjector\]: 1-character bug was causing invalid log messages and a memory leak (#6235, @jetstack-bot) + +## v1.12.2 + +v1.12.2 is a bugfix release, but includes a known issue. You should prefer +upgrading to the latest patch version available for 1.12. + +### Known issues + +- cainjector contains a memory leak due to re-assignment of a log variable (see https://github.com/cert-manager/cert-manager/issues/6217). The fix will be released in v1.12.3. See https://github.com/cert-manager/cert-manager/pull/6232 for context. + +### Changes + +- BUGFIX: `cmctl check api --wait 0` exited without output; we now make sure we perform the API check at least once (#6116, @jetstack-bot) + +## v1.12.1 + +The v1.12.1 release contains a couple dependency bumps and changes to ACME +external webhook library. Note that v1.12.1 contains a known issue, and you +should prefer upgrading to the latest patch version available for 1.12. + +### Known issues + +- [`cmctl` API check](https://cert-manager.io/docs/installation/verify/) is broken in v1.12.1. We suggest that you do not upgrade `cmctl` to this version. The fix will be released in v1.12.2. +See #6116 for context. +- cainjector contains a memory leak due to re-assignment of a log variable (see https://github.com/cert-manager/cert-manager/issues/6217). The fix will be released in v1.12.3. +See https://github.com/cert-manager/cert-manager/pull/6232 for context. + +### Other + +- Don't run API Priority and Fairness controller in webhook's extension apiserver ([#6085](https://github.com/cert-manager/cert-manager/pull/6085), [@irbekrm](https://github.com/irbekrm)) +- Adds a warning for folks to not use controller feature gates helm value to configure webhook feature gates ([#6100](https://github.com/cert-manager/cert-manager/pull/6100), [@irbekrm](https://github.com/irbekrm)) + +### Uncategorized + +- Updates Kubernetes libraries to `v0.27.2`. ([#6077](https://github.com/cert-manager/cert-manager/pull/6077), [@lucacome](https://github.com/lucacome)) +- Updates controller-runtime to `v0.15.0` ([#6098](https://github.com/cert-manager/cert-manager/pull/6098), [@lucacome](https://github.com/lucacome)) + +## v1.12.0 + cert-manager 1.12 brings support for JSON logging, a lower memory footprint, the support for ephemeral service account tokens with Vault, and the support of the `ingressClassName` field. We also improved on our ability to patch vulnerabilities. -## Major Themes +### Major Themes -### Support for JSON logging +#### Support for JSON logging JSON logs are now available in cert-manager! A massive thank you to [@malovme](https://github.com/malovme) for going the extra mile to get @@ -30,12 +93,12 @@ helm upgrade --install cert-manager jetstack/cert-manager --namespace cert-manag --set cainjector.extraArgs='{--logging-format=json}' ``` -### Lower memory footprint +#### Lower memory footprint In 1.12 we continued the work started in 1.11 to reduce cert-manager component's memory consumption. -#### Controller +##### Controller Caching of the full contents of all cluster `Secret`s can now be disabled by setting a `SecretsFilteredCaching` alpha feature gate to true. This will ensure @@ -60,7 +123,7 @@ Additionally, controller no longer watches and caches all `Pod` and `Service` resources. See [`cert-manager#5976`](https://github.com/cert-manager/cert-manager/pull/5976) for implementation. -#### Cainjector +##### Cainjector [Cainjector's](../concepts/ca-injector.md) control loops have been refactored, so by default it should consume up to half as much memory as before, see @@ -88,7 +151,7 @@ See [`cert-manager#5766`](https://github.com/cert-manager/cert-manager/pull/5766 A big thanks to everyone who put in time reporting and writing up issues describing performance problems in large scale installations. -### Faster Response to CVEs By Reducing Transitive Dependencies +#### Faster Response to CVEs By Reducing Transitive Dependencies In cert-manager 1.12, we have worked on reducing the impacts that unsupported dependencies have on our ability to patch CVEs. @@ -119,7 +182,7 @@ impact, and there should be no runtime impact either. You can read more about this change in the design document [`20230302.gomod.md`](https://github.com/cert-manager/cert-manager/blob/master/design/20230302.gomod.md). -### Support for ephemeral service account tokens in Vault +#### Support for ephemeral service account tokens in Vault cert-manager can now authenticate to Vault using ephemeral service account tokens (JWT). cert-manager already knew to authenticate to Vault using the @@ -135,7 +198,7 @@ authenticate to Vault. This change was implemented in the pull request [`cert-manager#5502`](https://github.com/cert-manager/cert-manager/pull/5502). -### Support for `ingressClassName` in the HTTP-01 solver +#### Support for `ingressClassName` in the HTTP-01 solver cert-manager now supports the `ingressClassName` field in the HTTP-01 solver. We recommend using `ingressClassName` instead of the field `class` in your Issuers @@ -143,7 +206,7 @@ and ClusterIssuers. > 📖 Read more about `ingressClassName` in the documentation page [HTTP01](../configuration/acme/http01/#ingressclassname). -### Liveness probe and healthz endpoint in the controller +#### Liveness probe and healthz endpoint in the controller A healthz HTTP server has been added to the controller component. It serves a `/livez` endpoint, which reports the health status of the leader election system. @@ -154,7 +217,7 @@ this will cause the controller to be restarted by the kubelet. > 📖 Read more about this new feature in [Best Practice: Use Liveness Probes](../installation/best-practice.md#use-liveness-probes). -## Community +### Community We extend our gratitude to all the open-source contributors who have made commits in this release, including: @@ -204,53 +267,9 @@ the Private CA Issuer. In addition, massive thanks to Jetstack (by Venafi) for contributing developer time and resources towards the continued maintenance of cert-manager projects. -## `v1.12.3`: changes since `v1.12.2` - -### Changes by Kind -#### Bugfixes +### Changes -- BUGFIX: 1-character bug was causing invalid log messages and a memory leak ([#6235](https://github.com/cert-manager/cert-manager/pull/6235), @jetstack-bot) - -## `v1.12.2`: changes since `v1.12.1` - -### Known issues - -- cainjector contains a memory leak due to re-assignment of a log variable (see https://github.com/cert-manager/cert-manager/issues/6217). The fix will be released in v1.12.3. -See https://github.com/cert-manager/cert-manager/pull/6232 for context. - -### Changes by Kind - -#### Bugfixes - -- BUGFIX: `cmctl check api --wait 0` exited without output; we now make sure we perform the API check at least once (#6116, @jetstack-bot) - - -## `v1.12.1`: changes since `v1.12.0` - -This release contains a couple dependency bumps and changes to ACME external webhook library. - -### Known issues - -- [`cmctl` API check](https://cert-manager.io/docs/installation/verify/) is broken in v1.12.1. We suggest that you do not upgrade `cmctl` to this version. The fix will be released in v1.12.2. -See #6116 for context. -- cainjector contains a memory leak due to re-assignment of a log variable (see https://github.com/cert-manager/cert-manager/issues/6217). The fix will be released in v1.12.3. -See https://github.com/cert-manager/cert-manager/pull/6232 for context. - -### Changes by Kind - -#### Other (Cleanup or Flake) - -- Don't run API Priority and Fairness controller in webhook's extension apiserver ([#6085](https://github.com/cert-manager/cert-manager/pull/6085), [@irbekrm](https://github.com/irbekrm)) -- Adds a warning for folks to not use controller feature gates helm value to configure webhook feature gates ([#6100](https://github.com/cert-manager/cert-manager/pull/6100), [@irbekrm](https://github.com/irbekrm)) - -#### Uncategorized - -- Updates Kubernetes libraries to `v0.27.2`. ([#6077](https://github.com/cert-manager/cert-manager/pull/6077), [@lucacome](https://github.com/lucacome)) -- Updates controller-runtime to `v0.15.0` ([#6098](https://github.com/cert-manager/cert-manager/pull/6098), [@lucacome](https://github.com/lucacome)) - -## `v1.12.0`: changes since `v1.11.0` - -### Feature +#### Feature - Helm: Added PodDisruptionBudgets for cert-manager components to the Helm chart (disabled by default). ([#3931](https://github.com/cert-manager/cert-manager/pull/3931), [@e96wic](https://github.com/e96wic)) - Added support for JSON logging (using `--logging-format=json`) ([#5828](https://github.com/cert-manager/cert-manager/pull/5828), [@malovme](https://github.com/malovme)) @@ -273,11 +292,11 @@ See https://github.com/cert-manager/cert-manager/pull/6232 for context. - The cainjector controller can now use server-side apply to patch mutatingwebhookconfigurations, validatingwebhookconfigurations, apiservices, and customresourcedefinitions. This feature is currently in alpha and is not enabled by default. To enable server-side apply for the cainjector, add the flag --feature-gates=ServerSideApply=true to the deployment. ([#5991](https://github.com/cert-manager/cert-manager/pull/5991), [@inteon](https://github.com/inteon)) - Helm: Egress 6443/TCP is now allowed in the webhook. This is required for OpenShift and OKD clusters for which the Kubernetes API server listens on port 6443 instead of 443. ([#5788](https://github.com/cert-manager/cert-manager/pull/5788), [@ExNG](https://github.com/ExNG)) -### Documentation +#### Documentation - Helm: the dead links in `values.yaml` are now working ([#5999](https://github.com/cert-manager/cert-manager/pull/5999), [@SgtCoDFish](https://github.com/SgtCoDFish)) -### Bug or Regression +#### Bug or Regression - When using the `literalSubject` field on a Certificate resource, the IPs, URIs, DNS names, and email addresses segments are now properly compared. ([#5747](https://github.com/cert-manager/cert-manager/pull/5747), [@inteon](https://github.com/inteon)) - When using the `jks` and `pkcs12` fields on a Certificate resource with a CA issuer that doesn't set the `ca.crt` in the Secret resource, cert-manager no longer loop trying to copy `ca.crt` into `truststore.jks` or `truststore.p12`. ([#5972](https://github.com/cert-manager/cert-manager/pull/5972), [@vinzent](https://github.com/vinzent)) @@ -290,7 +309,7 @@ See https://github.com/cert-manager/cert-manager/pull/6232 for context. - Upgrade to go 1.19.6 along with newer helm and containerd versions and updated base images ([#5813](https://github.com/cert-manager/cert-manager/pull/5813), [@SgtCoDFish](https://github.com/SgtCoDFish)) - cmctl: In order work around a hardcoded Kubernetes version in Helm, we now use a fake kube-apiserver version when generating the helm template when running `cmctl x install`. ([#5720](https://github.com/cert-manager/cert-manager/pull/5720), [@irbekrm](https://github.com/irbekrm)) -### Other (Cleanup or Flake) +#### Other (Cleanup or Flake) - ACME account registration is now re-verified if account key is manually changed. ([#5949](https://github.com/cert-manager/cert-manager/pull/5949), [@TrilokGeer](https://github.com/TrilokGeer)) - Add `make go-workspace` target for generating a go.work file for local development ([#5935](https://github.com/cert-manager/cert-manager/pull/5935), [@SgtCoDFish](https://github.com/SgtCoDFish)) @@ -320,7 +339,7 @@ See https://github.com/cert-manager/cert-manager/pull/6232 for context. - Validates that `certificate.spec.secretName` is a valid `Secret` name ([#5967](https://github.com/cert-manager/cert-manager/pull/5967), [@avi-08](https://github.com/avi-08)) - `certificate.spec.secretName` Secrets will now be labelled with `controller.cert-manager.io/fao` label ([#5660](https://github.com/cert-manager/cert-manager/pull/5660), [@irbekrm](https://github.com/irbekrm)) -### Uncategorized +#### Uncategorized - We have replaced our python boilerplate checker with an installed Go version, removing the need to have Python installed when developing or building cert-manager. ([#6000](https://github.com/cert-manager/cert-manager/pull/6000), [@SgtCoDFish](https://github.com/SgtCoDFish)) diff --git a/content/v1.12-docs/reference/api-docs.md b/content/v1.12-docs/reference/api-docs.md index 83285c0741..e766b7cfeb 100644 --- a/content/v1.12-docs/reference/api-docs.md +++ b/content/v1.12-docs/reference/api-docs.md @@ -5760,5 +5760,5 @@ description: >-

      - Generated with gen-crd-api-reference-docs on git commit 65bf16d. + Generated with gen-crd-api-reference-docs on git commit fe41951.

      From 123141f88a0e7cd5d709cac45886949a1429a64b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 19:53:48 +0200 Subject: [PATCH 113/264] release-process: sed command not compatible with BSD sed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 30d273485a..b1d86e75aa 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -198,11 +198,19 @@ page if a step is missing or if it is outdated. The steps below need to happen using `master` (**final release**) or `release-1.x` (**patch release**). The PR will be merged after the release. - 1. Go to the Generate `release-notes.md` using the instructions further below - (Ctrl+F and look for `github-release-description.md`). + Go to the section "Generate `github-release-description.md`" using the + instructions further below (Ctrl+F and look for + `github-release-description.md`). 2. Remove the "Dependencies" section. - 3. Edit any `release-note` block in the PR description that doesn't follow - the [release-note guidelines](../contributing/contributing-flow.md#release-note-guidelines) + 3. For each bullet point in the Markdown file, read the changelog entry and + check that it follows the [release-note + guidelines](../contributing/contributing-flow.md#release-note-guidelines). + If you find a changelog entry that doesn't follow the guidelines, then: + - Go to that PR and edit the PR description to change the contents of the + `release-note` block. + - Go back to the release notes page, and copy the same change into + `release-notes.md` (or re-generate the file). + and copy the same change into `release-notes.md` (or re-generate the file). 4. Add the section "Major themes" and "Community" by taking example on the @@ -211,11 +219,11 @@ page if a step is missing or if it is outdated. `@maelvls`) with actual links using the following command: ```bash - sed github-release-description.md \ + sed \ -e 's$#([0-9]+)$[#\1](https://github.com/cert-manager/cert-manager/pull/\1)$g' \ -e 's$@(\w+)$[@\1](https://github.com/\1)$g' \ -E \ - -i + github-release-description.md >release-notes.md ``` 6. Move `release-notes.md` to the website repo: From f6d5d41e38486ba8c52fd7f9fae5a2686824b748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 17:23:58 +0200 Subject: [PATCH 114/264] release-process: fix find command being too loose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index b1d86e75aa..34578b01f4 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -269,11 +269,11 @@ page if a step is missing or if it is outdated. +genversionwithcli "release-1.12" "$LATEST_VERSION" ``` - 4. (**final + patch release**) Bump all versions present in installation + 4. (**final + patch release of the latest minor version**) Bump all versions present in installation instructions. To find these versions: ```bash - find ./content/docs/installation -name '*.md' -not -path '*/upgrad**' -exec sed -i.bak 's/1.11../1.12.0/g' '{}' \; + find ./content/docs/installation -name '*.md' -not -path '*/upgrad**' -and -not -path '*supported-releases.md' -exec sed -i.bak 's/1.11./1.12.0/g' '{}' \; rm -f **/*.bak ``` From 7eb34ca81ca5c9abc596b78c5168b1c5e4edaaf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 17:47:30 +0200 Subject: [PATCH 115/264] release-process: jetstack -> cert-manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 34578b01f4..ad9f3dc8d1 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -269,8 +269,8 @@ page if a step is missing or if it is outdated. +genversionwithcli "release-1.12" "$LATEST_VERSION" ``` - 4. (**final + patch release of the latest minor version**) Bump all versions present in installation - instructions. To find these versions: + 4. (**final + patch release of the latest minor version**) Bump all versions + present in installation instructions. To find these versions: ```bash find ./content/docs/installation -name '*.md' -not -path '*/upgrad**' -and -not -path '*supported-releases.md' -exec sed -i.bak 's/1.11./1.12.0/g' '{}' \; @@ -316,8 +316,8 @@ page if a step is missing or if it is outdated. It should show: ```text - origin https://github.com/jetstack/cert-manager (fetch) - origin https://github.com/jetstack/cert-manager (push) + origin https://github.com/cert-manager/cert-manager (fetch) + origin https://github.com/cert-manager/cert-manager (push) ``` 7. Place yourself on the correct branch: From 876e03792b750010b174624e3cfb7c60bb9883ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 18:01:57 +0200 Subject: [PATCH 116/264] release-process: the update-cmd/ctl/ thing only works for 1.12 and above MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index ad9f3dc8d1..1874e99331 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -399,8 +399,8 @@ page if a step is missing or if it is outdated. > kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to > the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). -9. In this step, we make sure the Go module - `github.com/cert-manager/cert-manager/cmd/cmctl` can be imported by +9. **(1.12 and above)** In this step, we make sure the Go module + `github.com/cert-manager/cert-manager/cmd/ctl` can be imported by third-parties. First, create a temporary branch. @@ -410,20 +410,20 @@ page if a step is missing or if it is outdated. git checkout -b "update-cmd/ctl/$RELEASE_VERSION" ``` - Second, update the `cmd/cmctl`'s `go.mod` with the tag we just created: + Second, update the `cmd/ctl`'s `go.mod` with the tag we just created: ```bash # Must be run from the cert-manager repo folder. - cd cmd/cmctl + cd cmd/ctl go get github.com/cert-manager/cert-manager@$RELEASE_VERSION cd ../.. find . -name go.mod -not -path ./_bin/\* -exec dirname '{}' \; | xargs -L1 -I@ sh -c 'cd @; go mod tidy' git add **/go.mod **/go.sum - git commit -m"Update cmd/cmctl's go.mod to $RELEASE_VERSION" + git commit -m"Update cmd/ctl's go.mod to $RELEASE_VERSION" ``` - Third, create a tag for the `cmd/cmctl` module: + Third, create a tag for the `cmd/ctl` module: ```bash # Must be run from the cert-manager repo folder. From 17763b11476675aab1fcae3cef0a6ed455c0f5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 18:19:05 +0200 Subject: [PATCH 117/264] release-process: reformulate 1.10-specific parts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 60 +++++++++----------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 1874e99331..aaabcd95f0 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -495,46 +495,42 @@ page if a step is missing or if it is outdated. 4. **(final release only)** Write the "Community" section, following the example of past releases such as [v1.12.0](https://github.com/cert-manager/cert-manager/releases/tag/v1.12.0). If there are any users who didn't make code contributions but helped in other ways (testing, PR discussion, etc), be sure to thank them here! -11. Check that the build is complete and send Slack messages about the release: +11. Check that the build that was automatically triggered when you pushed the + tag is complete and send Slack messages about the release: - 1. For recent versions of cert-manager, the build will have been automatically - triggered by the tag being pushed earlier. You can check if it's complete on - the [GCB Build History](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). - - 2. If you're releasing an older version of cert-manager (earlier than 1.10) then the automatic build - will failed because the GCB config for that build wasn't backported. - In this case, you'll need to trigger the build manually using `cmrel`, which takes about 5 minutes: - - ```bash - # Must be run from the "cert-manager/release" repo folder. - cmrel makestage --ref=$RELEASE_VERSION - ``` - - This build takes ~5 minutes. It will build all container images and create - all the manifest files, sign Helm charts and upload everything to a storage - bucket on Google Cloud. These artifacts will then be published and released - in the next steps. - - 3. In any case, send a first Slack message to `#cert-manager-dev`: + 1. Send a first Slack message to `#cert-manager-dev`:

      Releasing 1.2.0-alpha.2 🧵

      -

      - 🔰 Please have a quick look at the build log as it might contain some unredacted - data that we forgot to hide. We try to make sure the sensitive data is - properly redacted but sometimes we forget to update this. -

      + 2. Check that the build completed in the [GCB Build + History](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). + +

      + 🔰 Please have a quick look at the build log as it might contain some unredacted + data that we forgot to hide. We try to make sure the sensitive data is + properly redacted but sometimes we forget to update this. +

      + + > **NOTE (1.10 and earlier):** If you're releasing an older version of + > cert-manager then the automatic build will failed because the GCB config + > for that build wasn't backported. In this case, you'll need to trigger the + > build manually using `cmrel`, which takes about 5 minutes: + > + > ```bash + > # Must be run from the "cert-manager/release" repo folder. + > cmrel makestage --ref=$RELEASE_VERSION + > ``` - 4. Send a second Slack message in reply to this first message with the - Cloud Build job link. For example, the message might look like: + 3. Copy the build logs URL and send a second Slack message in reply to this first message with the Cloud + Build job link. For example, the message might look like:

      - Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237 + cmrel makestage build logs: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237

      -12. Run `cmrel publish`: +13. Run `cmrel publish`: 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are valid. Run the following command: @@ -579,7 +575,7 @@ page if a step is missing or if it is outdated. Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237

      -13. Publish the GitHub release: +14. Publish the GitHub release: 1. Visit the draft GitHub release and paste in the release notes that you generated earlier. You will need to manually edit the content to match @@ -594,7 +590,7 @@ page if a step is missing or if it is outdated. 4. Click "Publish" to make the GitHub release live. -14. Merge the pull request containing the Helm chart: +15. Merge the pull request containing the Helm chart: The Helm charts for cert-manager are served using Cloudflare pages and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). @@ -606,7 +602,7 @@ page if a step is missing or if it is outdated. 4. Merge the PR 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). -15. **(final + patch releases)** Merge the 4 Website PRs: +16. **(final + patch releases)** Merge the 4 Website PRs: 1. Merge the PRs "Release Notes", "Upgrade Notes", and "Freeze And Bump Versions" that you have created previously. From fa52c9e57e6f41668ce35c3689cfe482e686cea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 18:40:24 +0200 Subject: [PATCH 118/264] release-process: fix release-notes already-ready URLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 59 +++++++++++--------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index aaabcd95f0..0b0d02f15e 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -476,16 +476,22 @@ page if a step is missing or if it is outdated. 2. Generate `github-release-description.md` with the following command: - ```bash - # Must be run from the cert-manager folder. - export GITHUB_TOKEN=*your-token* - git fetch origin $BRANCH - export START_SHA="$(git rev-list --reverse --ancestry-path $(git merge-base $START_TAG $BRANCH)..$BRANCH | head -1)" - release-notes --debug --repo-path cert-manager \ - --org cert-manager --repo cert-manager \ - --required-author "jetstack-bot" \ - --output github-release-description.md - ``` + ```bash + # Must be run from the cert-manager folder. + export GITHUB_TOKEN=*your-token* + git fetch origin $BRANCH + export START_SHA="$(git rev-list --reverse --ancestry-path $(git merge-base $START_TAG $BRANCH)..$BRANCH | head -1)" + release-notes --debug --repo-path cert-manager \ + --org cert-manager --repo cert-manager \ + --required-author "jetstack-bot" \ + --markdown-links=false \ + --output github-release-description.md + ``` + +

      + The GitHub token **does not need any scope**. The token is required + only to avoid rate-limits imposed on anonymous API users. +

      The GitHub token **does not need any scope**. The token is required @@ -504,33 +510,34 @@ page if a step is missing or if it is outdated. Releasing 1.2.0-alpha.2 🧵

      - 2. Check that the build completed in the [GCB Build - History](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). + 2. Check that the build completed in the + [GCB Build History](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). -

      - 🔰 Please have a quick look at the build log as it might contain some unredacted - data that we forgot to hide. We try to make sure the sensitive data is - properly redacted but sometimes we forget to update this. -

      +

      + 🔰 Please have a quick look at the build log as it might contain some unredacted + data that we forgot to hide. We try to make sure the sensitive data is + properly redacted but sometimes we forget to update this. +

      > **NOTE (1.10 and earlier):** If you're releasing an older version of - > cert-manager then the automatic build will failed because the GCB config - > for that build wasn't backported. In this case, you'll need to trigger the - > build manually using `cmrel`, which takes about 5 minutes: + > cert-manager then the automatic build will failed because the GCB + > config for that build wasn't backported. In this case, you'll need to + > trigger the build manually using `cmrel`, which takes about 5 minutes: > > ```bash > # Must be run from the "cert-manager/release" repo folder. > cmrel makestage --ref=$RELEASE_VERSION > ``` - 3. Copy the build logs URL and send a second Slack message in reply to this first message with the Cloud - Build job link. For example, the message might look like: + 3. Copy the build logs URL and send a second Slack message in reply to this + first message with the Cloud Build job link. For example, the message + might look like:

      cmrel makestage build logs: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237

      -13. Run `cmrel publish`: +12. Run `cmrel publish`: 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are valid. Run the following command: @@ -575,7 +582,7 @@ page if a step is missing or if it is outdated. Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237

      -14. Publish the GitHub release: +13. Publish the GitHub release: 1. Visit the draft GitHub release and paste in the release notes that you generated earlier. You will need to manually edit the content to match @@ -590,7 +597,7 @@ page if a step is missing or if it is outdated. 4. Click "Publish" to make the GitHub release live. -15. Merge the pull request containing the Helm chart: +14. Merge the pull request containing the Helm chart: The Helm charts for cert-manager are served using Cloudflare pages and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). @@ -602,7 +609,7 @@ page if a step is missing or if it is outdated. 4. Merge the PR 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). -16. **(final + patch releases)** Merge the 4 Website PRs: +15. **(final + patch releases)** Merge the 4 Website PRs: 1. Merge the PRs "Release Notes", "Upgrade Notes", and "Freeze And Bump Versions" that you have created previously. From d2569b794ccaecd3ca191ba8ed8684aba5300d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 18:52:13 +0200 Subject: [PATCH 119/264] release-process: explain that you need to push the branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 0b0d02f15e..0795302b8e 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -436,8 +436,16 @@ page if a step is missing or if it is outdated. [how-are-versions-of-a-sub-module-managed]: https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 - Then, open a PR to merge that change and go back to the release branch - with the following commands: + Then, push the branch to your fork of cert-manager. For example: + + ```bash + # Must be run from the cert-manager repo folder. + gh repo fork --remote-name fork + git push -u fork "update-cmd/ctl/$RELEASE_VERSION" + ``` + + Then, open a PR to merge that change and go back to the release branch with + the following commands: ```bash gh pr create \ From 42faf39bcd8fb4b7dbe50e9d5f56e744e4de4105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 18:57:31 +0200 Subject: [PATCH 120/264] release-process: fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 0795302b8e..f9563462a9 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -461,11 +461,11 @@ page if a step is missing or if it is outdated. EOF ``` - Finally, get back to the main tag: + Finally, get back to the branch you were on initially: - ```bash - git checkout $RELEASE_VERSION - ``` + ```bash + git checkout $BRANCH + ``` 10. In this section, we will be creating the description for the GitHub Release. From 394bbbece45348dd69bd068d47e60d0cc3294ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 1 Sep 2023 20:00:34 +0200 Subject: [PATCH 121/264] release-process: fix spelling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- .spelling | 1 + content/docs/contributing/release-process.md | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.spelling b/.spelling index a8e5948b8b..4d14dc2507 100644 --- a/.spelling +++ b/.spelling @@ -102,6 +102,7 @@ CertificateSecretTemplate CertificateSigningRequest CertificateSigningRequests Changelog +changelog ChartMuseum CloudDNS CloudFlare diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index f9563462a9..878553a5e8 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -203,8 +203,7 @@ page if a step is missing or if it is outdated. `github-release-description.md`). 2. Remove the "Dependencies" section. 3. For each bullet point in the Markdown file, read the changelog entry and - check that it follows the [release-note - guidelines](../contributing/contributing-flow.md#release-note-guidelines). + check that it follows the [release-note guidelines](../contributing/contributing-flow.md#release-note-guidelines). If you find a changelog entry that doesn't follow the guidelines, then: - Go to that PR and edit the PR description to change the contents of the `release-note` block. From 34616ab08b2c9a1669c8982374a7362e6ca86194 Mon Sep 17 00:00:00 2001 From: Vasily Vasilyev Date: Sun, 4 Jun 2023 23:20:50 +0300 Subject: [PATCH 122/264] Add link to zilore webhook Signed-off-by: Vasily Vasilyev --- content/docs/configuration/acme/dns01/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/docs/configuration/acme/dns01/README.md b/content/docs/configuration/acme/dns01/README.md index b9240f8045..997a26bedb 100644 --- a/content/docs/configuration/acme/dns01/README.md +++ b/content/docs/configuration/acme/dns01/README.md @@ -180,6 +180,7 @@ Links to these supported providers along with their documentation are below: - [`cert-manager-webhook-yandex-cloud`](https://github.com/malinink/cert-manager-webhook-yandex-cloud) - [`cert-manager-webhook-netcup`](https://github.com/aellwein/cert-manager-webhook-netcup) - [`cert-manager-webhook-pdns`](https://github.com/zachomedia/cert-manager-webhook-pdns) +- [`cert-manager-webhook-zilore`](https://gitlab.com/zilore/cert-manager-webhook-zilore) You can find more information on how to configure webhook providers [here](./webhook.md). From 09bf507fc99250cdb426ac368f6bfcc05140e183 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 07:53:49 +0000 Subject: [PATCH 123/264] build(deps): bump webpack from 5.70.0 to 5.88.2 Bumps [webpack](https://github.com/webpack/webpack) from 5.70.0 to 5.88.2. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.70.0...v5.88.2) --- updated-dependencies: - dependency-name: webpack dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 635 +++++++++++++++++++++------------------------- 1 file changed, 294 insertions(+), 341 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63f381dda1..62d12bf24a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -850,9 +850,9 @@ "dev": true }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "peer": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", @@ -864,9 +864,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "peer": true, "engines": { "node": ">=6.0.0" @@ -882,9 +882,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -897,13 +897,13 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@mdx-js/loader": { @@ -1414,9 +1414,9 @@ } }, "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" }, "node_modules/@types/estree-jsx": { "version": "0.0.1", @@ -1654,148 +1654,148 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "peer": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "peer": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" } }, @@ -1853,9 +1853,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "bin": { "acorn": "bin/acorn" }, @@ -1864,9 +1864,9 @@ } }, "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "peer": true, "peerDependencies": { "acorn": "^8" @@ -2539,6 +2539,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/compare-versions": { "version": "6.0.0-rc.1", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.0.0-rc.1.tgz", @@ -2986,9 +2991,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -3066,9 +3071,9 @@ } }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", + "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", "peer": true }, "node_modules/es-set-tostringtag": { @@ -5471,17 +5476,10 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "peer": true - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -5868,12 +5866,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "node_modules/markdown-spellcheck/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/markdown-spellcheck/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -9788,9 +9780,9 @@ } }, "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -9833,9 +9825,9 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "peer": true, "dependencies": { "randombytes": "^2.1.0" @@ -10575,13 +10567,13 @@ } }, "node_modules/terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "version": "5.19.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.4.tgz", + "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", "peer": true, "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -10593,16 +10585,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "peer": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" }, "engines": { "node": ">= 10.13.0" @@ -10626,21 +10618,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "peer": true - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -11497,9 +11474,9 @@ "dev": true }, "node_modules/watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -11510,34 +11487,34 @@ } }, "node_modules/webpack": { - "version": "5.70.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", - "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", + "version": "5.88.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", + "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "bin": { @@ -12366,9 +12343,9 @@ "dev": true }, "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "peer": true, "requires": { "@jridgewell/set-array": "^1.0.1", @@ -12377,9 +12354,9 @@ } }, "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "peer": true }, "@jridgewell/set-array": { @@ -12389,9 +12366,9 @@ "peer": true }, "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "peer": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", @@ -12404,13 +12381,13 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "peer": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@mdx-js/loader": { @@ -12748,9 +12725,9 @@ } }, "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" }, "@types/estree-jsx": { "version": "0.0.1", @@ -12940,148 +12917,148 @@ } }, "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "peer": true, "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "peer": true }, "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "peer": true }, "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", "peer": true }, "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "peer": true, "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "peer": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "peer": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "peer": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "peer": true }, "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" } }, @@ -13130,14 +13107,14 @@ "dev": true }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" }, "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "peer": true, "requires": {} }, @@ -13629,6 +13606,11 @@ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==" }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "compare-versions": { "version": "6.0.0-rc.1", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.0.0-rc.1.tgz", @@ -13962,9 +13944,9 @@ "dev": true }, "enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -14027,9 +14009,9 @@ } }, "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", + "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", "peer": true }, "es-set-tostringtag": { @@ -15790,17 +15772,10 @@ "argparse": "^2.0.1" } }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "peer": true - }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema-traverse": { "version": "0.4.1", @@ -16114,12 +16089,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -18884,9 +18853,9 @@ } }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "peer": true, "requires": { "@types/json-schema": "^7.0.8", @@ -18913,9 +18882,9 @@ } }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "peer": true, "requires": { "randombytes": "^2.1.0" @@ -19465,44 +19434,28 @@ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "version": "5.19.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.4.tgz", + "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", "peer": true, "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "peer": true - } } }, "terser-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "peer": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true - } + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" } }, "text-table": { @@ -20165,9 +20118,9 @@ "dev": true }, "watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "peer": true, "requires": { "glob-to-regexp": "^0.4.1", @@ -20175,34 +20128,34 @@ } }, "webpack": { - "version": "5.70.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", - "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", + "version": "5.88.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", + "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", "peer": true, "requires": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "dependencies": { From 88603e42bd8c79de4b16e36132cf35052fdd5afa Mon Sep 17 00:00:00 2001 From: nrogerarkhn Date: Mon, 19 Jun 2023 17:03:20 +0200 Subject: [PATCH 124/264] Add queried endpoint to check Vault status when initializing issuer The Vault Issuer queries an endpoint to check is the Vault instance is healthy. When configuring the Issuer with a remote Vault instance protected behind a firewall, the 'Vault sealed or unintialized' error could be thrown when the endpoint could simply not be reached. There was no sign of this endpoint in the docs, so adding it for commodity. Signed-off-by: nrogerarkhn --- content/docs/configuration/vault.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/content/docs/configuration/vault.md b/content/docs/configuration/vault.md index c5d6df0923..26d0707f67 100644 --- a/content/docs/configuration/vault.md +++ b/content/docs/configuration/vault.md @@ -369,9 +369,15 @@ Kubernetes 1.24 and above. ## Verifying the issuer Deployment Once the Vault issuer has been deployed, it will be marked as ready if the -configuration is valid. Replace `issuers` here with `clusterissuers` if that is what has +configuration is valid. Replace `issuers` below with `clusterissuers` if that is what has been deployed. +The Vault issuer tests your Vault instance by querying the `v1/sys/health` +endpoint, to ensure your Vault instance is unsealed and initialized before +requesting certificates. The result of that query will populate the `STATUS` +column + + ```bash $ kubectl get issuers vault-issuer -n sandbox -o wide NAME READY STATUS AGE @@ -379,4 +385,4 @@ vault-issuer True Vault verified 2m ``` Certificates are now ready to be requested by using the Vault issuer named -`vault-issuer` within the `sandbox` namespace. \ No newline at end of file +`vault-issuer` within the `sandbox` namespace. From f7f6687333bdc23488e92b4e47fac8b6f50efbe8 Mon Sep 17 00:00:00 2001 From: nrogerarkhn Date: Mon, 19 Jun 2023 17:17:28 +0200 Subject: [PATCH 125/264] Fix typos Signed-off-by: nrogerarkhn --- content/docs/configuration/vault.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/docs/configuration/vault.md b/content/docs/configuration/vault.md index 26d0707f67..f2b15b5435 100644 --- a/content/docs/configuration/vault.md +++ b/content/docs/configuration/vault.md @@ -375,8 +375,7 @@ been deployed. The Vault issuer tests your Vault instance by querying the `v1/sys/health` endpoint, to ensure your Vault instance is unsealed and initialized before requesting certificates. The result of that query will populate the `STATUS` -column - +column. ```bash $ kubectl get issuers vault-issuer -n sandbox -o wide From e1f9ca8dfd3ebec62c52e5a4a86f94b9420a0094 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 08:05:03 +0000 Subject: [PATCH 126/264] build(deps-dev): bump @antfu/utils from 0.7.2 to 0.7.6 Bumps [@antfu/utils](https://github.com/antfu/utils) from 0.7.2 to 0.7.6. - [Release notes](https://github.com/antfu/utils/releases) - [Commits](https://github.com/antfu/utils/compare/v0.7.2...v0.7.6) --- updated-dependencies: - dependency-name: "@antfu/utils" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62d12bf24a..cf05b43585 100644 --- a/package-lock.json +++ b/package-lock.json @@ -206,9 +206,9 @@ } }, "node_modules/@antfu/utils": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.2.tgz", - "integrity": "sha512-vy9fM3pIxZmX07dL+VX1aZe7ynZ+YyB0jY+jE6r3hOK6GNY2t6W8rzpFC4tgpbXUYABkFQwgJq2XYXlxbXAI0g==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.6.tgz", + "integrity": "sha512-pvFiLP2BeOKA/ZOS6jxx4XhKzdVLHDhGlFEaZ2flWWYf2xOqVniqpk38I04DFRyz+L0ASggl7SkItTc+ZLju4w==", "dev": true, "funding": { "url": "https://github.com/sponsors/antfu" @@ -11987,9 +11987,9 @@ } }, "@antfu/utils": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.2.tgz", - "integrity": "sha512-vy9fM3pIxZmX07dL+VX1aZe7ynZ+YyB0jY+jE6r3hOK6GNY2t6W8rzpFC4tgpbXUYABkFQwgJq2XYXlxbXAI0g==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.6.tgz", + "integrity": "sha512-pvFiLP2BeOKA/ZOS6jxx4XhKzdVLHDhGlFEaZ2flWWYf2xOqVniqpk38I04DFRyz+L0ASggl7SkItTc+ZLju4w==", "dev": true }, "@babel/code-frame": { From 88efec3e8f725f570b935a26ff3bba13c922445e Mon Sep 17 00:00:00 2001 From: Compy Date: Sun, 12 Jun 2022 19:51:10 -0500 Subject: [PATCH 127/264] Adding accessKeyIDSecretRef feature documentation Signed-off-by: Compy --- content/docs/configuration/acme/dns01/route53.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/content/docs/configuration/acme/dns01/route53.md b/content/docs/configuration/acme/dns01/route53.md index f18bbf128c..e441b3b0c3 100644 --- a/content/docs/configuration/acme/dns01/route53.md +++ b/content/docs/configuration/acme/dns01/route53.md @@ -56,7 +56,7 @@ not have to store permanent credentials in a secret. cert-manager supports two ways of specifying credentials: -- explicit by providing a `accessKeyID` and `secretAccessKey` +- explicit by providing an `accessKeyID` or an `accessKeyIDSecretRef`, and a `secretAccessKeySecretRef` - or implicit (using [metadata service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) or [environment variables or credentials @@ -168,7 +168,13 @@ spec: dns01: route53: region: eu-central-1 + # The AWS access key ID can be specified using the literal accessKeyID parameter + # or retrieved from a secret using the accessKeyIDSecretRef + # If using accessKeyID, omit the accessKeyIDSecretRef parameter and vice-versa accessKeyID: AKIAIOSFODNN7EXAMPLE + accessKeyIDSecretRef: + name: prod-route53-credentials-secret + key: access-key-id secretAccessKeySecretRef: name: prod-route53-credentials-secret key: secret-access-key From e7ae3fa57520888f7ad993446e63785f582e63d4 Mon Sep 17 00:00:00 2001 From: Craig Trought Date: Fri, 16 Jun 2023 10:25:25 -0400 Subject: [PATCH 128/264] document ingress/gateway subject annotations added in v1.12 Signed-off-by: Craig Trought --- content/docs/usage/gateway.md | 36 +++++++++++++++++++++++++++++++++ content/docs/usage/ingress.md | 38 ++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/content/docs/usage/gateway.md b/content/docs/usage/gateway.md index 9aa05cf1a1..9f2a2633d5 100644 --- a/content/docs/usage/gateway.md +++ b/content/docs/usage/gateway.md @@ -369,6 +369,42 @@ Certificate resources: - `cert-manager.io/common-name`: (optional) this annotation allows you to configure `spec.commonName` for the Certificate to be generated. +- `cert-manager.io/email-sans`: (optional) this annotation allows you to + configure `spec.emailAddresses` field for the Certificate to be generated. + Supports comma-separated values e.g. "me@example.com,you@example.com" + +- `cert-manager.io/subject-organizations`: (optional) this annotation allows you to + configure `spec.subject.organizations` field for the Certificate to be generated. + Supports comma-separated values e.g. "Company 1,Company 2" + +- `cert-manager.io/subject-organizationalunits`: (optional) this annotation allows you to + configure `spec.subject.organizationalUnits` field for the Certificate to be generated. + Supports comma-separated values e.g. "IT Services,Cloud Services" + +- `cert-manager.io/subject-countries`: (optional) this annotation allows you to + configure `spec.subject.countries` field for the Certificate to be generated. + Supports comma-separated values e.g. "Country 1,Country 2" + +- `cert-manager.io/subject-provinces`: (optional) this annotation allows you to + configure `spec.subject.provinces` field for the Certificate to be generated. + Supports comma-separated values e.g. "Province 1,Province 2" + +- `cert-manager.io/subject-localities`: (optional) this annotation allows you to + configure `spec.subject.localities` field for the Certificate to be generated. + Supports comma-separated values e.g. "City 1,City 2" + +- `cert-manager.io/subject-postalcodes`: (optional) this annotation allows you to + configure `spec.subject.postalCodes` field for the Certificate to be generated. + Supports comma-separated values e.g. "123ABC,456DEF" + +- `cert-manager.io/subject-streetaddresses`: (optional) this annotation allows you to + configure `spec.subject.streetAddresses` field for the Certificate to be generated. + Supports comma-separated values e.g. "123 Example St,456 Other Blvd" + +- `cert-manager.io/subject-serialnumber`: (optional) this annotation allows you to + configure `spec.subject.serialNumber` field for the Certificate to be generated. + Supports comma-separated values e.g. "123 Example St,456 Other Blvd" + - ` cert-manager.io/duration`: (optional) this annotation allows you to configure `spec.duration` field for the Certificate to be generated. diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index e70c77390f..6a5ee35db4 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -102,6 +102,42 @@ trigger Certificate resources to be automatically created: - `cert-manager.io/common-name`: (optional) this annotation allows you to configure `spec.commonName` for the Certificate to be generated. +- `cert-manager.io/email-sans`: (optional) this annotation allows you to + configure `spec.emailAddresses` field for the Certificate to be generated. + Supports comma-separated values e.g. "me@example.com,you@example.com" + +- `cert-manager.io/subject-organizations`: (optional) this annotation allows you to + configure `spec.subject.organizations` field for the Certificate to be generated. + Supports comma-separated values e.g. "Company 1,Company 2" + +- `cert-manager.io/subject-organizationalunits`: (optional) this annotation allows you to + configure `spec.subject.organizationalUnits` field for the Certificate to be generated. + Supports comma-separated values e.g. "IT Services,Cloud Services" + +- `cert-manager.io/subject-countries`: (optional) this annotation allows you to + configure `spec.subject.countries` field for the Certificate to be generated. + Supports comma-separated values e.g. "Country 1,Country 2" + +- `cert-manager.io/subject-provinces`: (optional) this annotation allows you to + configure `spec.subject.provinces` field for the Certificate to be generated. + Supports comma-separated values e.g. "Province 1,Province 2" + +- `cert-manager.io/subject-localities`: (optional) this annotation allows you to + configure `spec.subject.localities` field for the Certificate to be generated. + Supports comma-separated values e.g. "City 1,City 2" + +- `cert-manager.io/subject-postalcodes`: (optional) this annotation allows you to + configure `spec.subject.postalCodes` field for the Certificate to be generated. + Supports comma-separated values e.g. "123ABC,456DEF" + +- `cert-manager.io/subject-streetaddresses`: (optional) this annotation allows you to + configure `spec.subject.streetAddresses` field for the Certificate to be generated. + Supports comma-separated values e.g. "123 Example St,456 Other Blvd" + +- `cert-manager.io/subject-serialnumber`: (optional) this annotation allows you to + configure `spec.subject.serialNumber` field for the Certificate to be generated. + Supports comma-separated values e.g. "123 Example St,456 Other Blvd" + - ` cert-manager.io/duration`: (optional) this annotation allows you to configure `spec.duration` field for the Certificate to be generated. @@ -172,4 +208,4 @@ guide](../installation/README.md). ## Troubleshooting -If you do not see a `Certificate` resource being created after applying the ingress-shim annotations check that at least `cert-manager.io/issuer` or `cert-manager.io/cluster-issuer` is set. If you want to use `kubernetes.io/tls-acme: "true"` make sure to have checked all steps above and you might want to look for errors in the cert-manager pod logs if not resolved. \ No newline at end of file +If you do not see a `Certificate` resource being created after applying the ingress-shim annotations check that at least `cert-manager.io/issuer` or `cert-manager.io/cluster-issuer` is set. If you want to use `kubernetes.io/tls-acme: "true"` make sure to have checked all steps above and you might want to look for errors in the cert-manager pod logs if not resolved. From a09d1b272c30651ac1a1ae6dea67fc0805cda0ce Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:24:04 +0200 Subject: [PATCH 129/264] fix copy-paste error for serialnumber Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/usage/gateway.md | 2 +- content/docs/usage/ingress.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/usage/gateway.md b/content/docs/usage/gateway.md index 9f2a2633d5..45f8fe62b7 100644 --- a/content/docs/usage/gateway.md +++ b/content/docs/usage/gateway.md @@ -403,7 +403,7 @@ Certificate resources: - `cert-manager.io/subject-serialnumber`: (optional) this annotation allows you to configure `spec.subject.serialNumber` field for the Certificate to be generated. - Supports comma-separated values e.g. "123 Example St,456 Other Blvd" + Supports comma-separated values e.g. "10978342379280287615,1111144445555522228888" - ` cert-manager.io/duration`: (optional) this annotation allows you to configure `spec.duration` field for the Certificate to be generated. diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index 6a5ee35db4..ecc5cf23b3 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -136,7 +136,7 @@ trigger Certificate resources to be automatically created: - `cert-manager.io/subject-serialnumber`: (optional) this annotation allows you to configure `spec.subject.serialNumber` field for the Certificate to be generated. - Supports comma-separated values e.g. "123 Example St,456 Other Blvd" + Supports comma-separated values e.g. "10978342379280287615,1111144445555522228888" - ` cert-manager.io/duration`: (optional) this annotation allows you to configure `spec.duration` field for the Certificate to be generated. From f0710d19d6dd7cc3691948c132618d6db2f90415 Mon Sep 17 00:00:00 2001 From: oernii Date: Fri, 26 May 2023 15:17:44 +0000 Subject: [PATCH 130/264] Add deletion of validatingwebhookconfigurations to stuck uninstall Signed-off-by: oernii --- content/docs/installation/kubectl.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index 655f0ecbdf..97c74e810d 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -117,6 +117,11 @@ First, delete existing cert-manager webhook configurations, if any: kubectl delete mutatingwebhookconfigurations cert-manager-webhook ``` +Then try do delete the corresponding validatingwebhookconfigurations +```bash +kubectl delete validatingwebhookconfigurations cert-manager-webhook +``` + Then change the `.metadata.finalizers` field to an empty list by editing the challenge resource: ```bash From 37857b0369eb92a34e605bcf419168e18f2f8027 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:26:05 +0200 Subject: [PATCH 131/264] apply review feedback Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/installation/kubectl.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index 97c74e810d..4c5d8ab368 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -115,10 +115,6 @@ First, delete existing cert-manager webhook configurations, if any: ```bash kubectl delete mutatingwebhookconfigurations cert-manager-webhook -``` - -Then try do delete the corresponding validatingwebhookconfigurations -```bash kubectl delete validatingwebhookconfigurations cert-manager-webhook ``` From aef67422357bffe2bf51ea9dda1db9b0e0aab969 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:42:18 +0200 Subject: [PATCH 132/264] Add multiple ingresses usage section Signed-off-by: Fernanda Martins --- content/docs/usage/ingress.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index e70c77390f..3cd4f6c44a 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -134,6 +134,10 @@ trigger Certificate resources to be automatically created: configure `spec.privateKey.rotationPolicy` field to set the rotation policy of the private key for a Certificate. Valid values are `Never` and `Always`. If unset a rotation policy `Never` will be used. +## Generate multiple certificates with multiple ingresses + +If you need to generate certificates from multiple ingresses make sure it has the issuer annotation. +Besides the annotation, it is necessary that each ingress possess a unique `tls.secretName` ## Optional Configuration From 6d5007ca17cf0146ac401ea48de582ea2d5f0022 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:55:17 +0200 Subject: [PATCH 133/264] rename the Configuration and usage top-level menu items Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 362e0f3d4d..b87e4703f8 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -195,7 +195,7 @@ ] }, { - "title": "Configuration", + "title": "Configuring Issuers", "routes": [ { "title": "Introduction", @@ -291,7 +291,7 @@ ] }, { - "title": "Usage", + "title": "Requesting Certificates", "routes": [ { "title": "Introduction", From 8a93e63b62c06634819d3683566c8c322b5d0b6d Mon Sep 17 00:00:00 2001 From: Yuedong Wu Date: Tue, 5 Sep 2023 10:59:08 +0000 Subject: [PATCH 134/264] Fix broken kyverno links Signed-off-by: GitHub --- content/docs/installation/best-practice.md | 2 +- content/v1.12-docs/installation/best-practice.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 1fd81277ae..2db8f3b153 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -107,7 +107,7 @@ tweak the [various liveness probe time settings and thresholds](https://github.c ## Restrict Auto-Mount of Service Account Tokens -This recommendation is described in the [Kyverno Policy Catalogue](https://kyverno.io/policies/other/restrict_automount_sa_token/restrict_automount_sa_token/) as follows: +This recommendation is described in the [Kyverno Policy Catalogue](https://kyverno.io/policies/other/res/restrict-automount-sa-token/restrict-automount-sa-token/) as follows: > Kubernetes automatically mounts ServiceAccount credentials in each Pod. The > ServiceAccount may be assigned roles allowing Pods to access API resources. > Blocking this ability is an extension of the least privilege best practice and diff --git a/content/v1.12-docs/installation/best-practice.md b/content/v1.12-docs/installation/best-practice.md index f2ca51b603..23f5ca984a 100644 --- a/content/v1.12-docs/installation/best-practice.md +++ b/content/v1.12-docs/installation/best-practice.md @@ -16,7 +16,7 @@ and in that case you can modify the installation configuration using Helm chart ## Restrict Auto-Mount of Service Account Tokens -This recommendation is described in the [Kyverno Policy Catalogue](https://kyverno.io/policies/other/restrict_automount_sa_token/restrict_automount_sa_token/) as follows: +This recommendation is described in the [Kyverno Policy Catalogue](https://kyverno.io/policies/other/res/restrict-automount-sa-token/restrict-automount-sa-token/) as follows: > Kubernetes automatically mounts ServiceAccount credentials in each Pod. The > ServiceAccount may be assigned roles allowing Pods to access API resources. > Blocking this ability is an extension of the least privilege best practice and From ee3017f31d1fda74b3e4fceeb90245710cd1fc94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 5 Sep 2023 20:52:49 +0200 Subject: [PATCH 135/264] release-process: the "find" trick was completely broken! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 878553a5e8..87839cb85f 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -269,17 +269,17 @@ page if a step is missing or if it is outdated. ``` 4. (**final + patch release of the latest minor version**) Bump all versions - present in installation instructions. To find these versions: + present in installation instructions. To update these versions: ```bash - find ./content/docs/installation -name '*.md' -not -path '*/upgrad**' -and -not -path '*supported-releases.md' -exec sed -i.bak 's/1.11./1.12.0/g' '{}' \; + sed -i.bak 's/1.12.[0-9]/1.12.4/g' content/docs/installation/{README.md,code-signing.md,helm.md,kubectl.md,operator-lifecycle-manager.md} rm -f **/*.bak ``` To check that all mentions of that version are gone, run: ```bash - grep -R -n -F 'v1.11.' content/docs/installation + grep -R -n -F 'v1.11.[0-9]' content/docs/installation ``` 5. (**final release only**) Freeze the `docs/` folder by creating a copy , From 2837a617ee934af2dca969542225f76f695ff4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Tue, 5 Sep 2023 20:53:33 +0200 Subject: [PATCH 136/264] release 1.12.4: update installation instructions to 1.12.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/installation/README.md | 2 +- content/docs/installation/code-signing.md | 2 +- content/docs/installation/helm.md | 10 +++++----- content/docs/installation/kubectl.md | 2 +- .../docs/installation/operator-lifecycle-manager.md | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/content/docs/installation/README.md b/content/docs/installation/README.md index e3d09c6c01..159f0195b0 100644 --- a/content/docs/installation/README.md +++ b/content/docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.4/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/docs/installation/code-signing.md b/content/docs/installation/code-signing.md index 4f85faf92e..32696d151e 100644 --- a/content/docs/installation/code-signing.md +++ b/content/docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.12.0 # change as needed +IMAGE_TAG=v1.12.4 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index eb3da6c977..94116a0109 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -47,7 +47,7 @@ section below for details on each method. > Recommended for production installations ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.4/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -70,7 +70,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.0 \ + --version v1.12.4 \ # --set installCRDs=true ``` @@ -83,7 +83,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.0 \ + --version v1.12.4 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -114,7 +114,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.12.0 + version: v1.12.4 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -148,7 +148,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.0 \ + --version v1.12.4 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index 655f0ecbdf..7a4f8b909d 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -19,7 +19,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.4/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index 52fa7ad06b..d34cdff2be 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.12.0 \ +kubectl patch csv cert-manager.v1.12.4 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` From 881c6ec6ca0e945fa72f2c9f150fc20462f18c36 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 6 Sep 2023 16:22:31 +0200 Subject: [PATCH 137/264] use same method for creating on-page references across all pages Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/configuration/acme/README.md | 6 ++--- .../docs/configuration/acme/http01/README.md | 9 ++++--- content/docs/faq/README.md | 6 ++--- .../docs/installation/supported-releases.md | 27 ++++++++++++------- content/docs/troubleshooting/webhook.md | 3 --- content/docs/usage/certificate.md | 12 ++++++--- 6 files changed, 36 insertions(+), 27 deletions(-) diff --git a/content/docs/configuration/acme/README.md b/content/docs/configuration/acme/README.md index 51a9014ac1..47eb22c9ee 100644 --- a/content/docs/configuration/acme/README.md +++ b/content/docs/configuration/acme/README.md @@ -367,11 +367,9 @@ spec: ``` -## Alternative Certificate Chains - {/* The empty link below preserves old links to #alternative-certificate-chain", which matched the old title of this section */} - - + +## Alternative Certificate Chains It's possible to choose alternative certificate chains when fetching a certificate from an ACME server. This allows issuers to gracefully roll people over to a new root certificate during a transition period; the most famous example was the Let's Encrypt ["ISRG Root" changeover](https://community.letsencrypt.org/t/transition-to-isrgs-root-delayed-until-jan-11-2021/125516). diff --git a/content/docs/configuration/acme/http01/README.md b/content/docs/configuration/acme/http01/README.md index e8663f6fdd..2e3e8242af 100644 --- a/content/docs/configuration/acme/http01/README.md +++ b/content/docs/configuration/acme/http01/README.md @@ -101,7 +101,8 @@ solver, potentially incurring additional cost. -

      `serviceType`

      + +### `serviceType` In rare cases it might be not possible/desired to use `NodePort` as type for the HTTP01 challenge response service, e.g. because of Kubernetes limit @@ -348,7 +349,8 @@ spec: After the Certificate is issued, the HTTPRoute is deleted. -

      `labels`

      + +### `labels` These labels are copied into the temporary HTTPRoute created by cert-manager for solving the HTTP-01 challenge. These labels must match one of the Gateway @@ -357,7 +359,8 @@ resources on your cluster. The matched Gateway have a listener on port 80. Note that when the labels do not match any Gateway on your cluster, cert-manager will create the temporary HTTPRoute challenge and nothing will happen. -

      `serviceType`

      + +### `serviceType` This field has the same meaning as the [`http01.ingress.serviceType`](#ingress-service-type). diff --git a/content/docs/faq/README.md b/content/docs/faq/README.md index 9bb31f91b5..6f885591a7 100644 --- a/content/docs/faq/README.md +++ b/content/docs/faq/README.md @@ -91,11 +91,9 @@ cert-manager publishes all events to the Kubernetes events mechanism, you can ge Due to the nature of the Kubernetes event mechanism these will be purged after a while. If you're using a dedicated logging system it might be able or is already also storing Kubernetes events. -### What happens if issuance fails? Will it be retried? - {/* This empty link preserves old links to #what-happens-if-a-renewal-doesn't happen?-will-it-be-tried-again-after-some-time?", which matched the old title of this section */} - - + +### What happens if issuance fails? Will it be retried? cert-manager will retry a failed issuance except for a few rare edge cases where manual intervention is needed. diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 841c38b8ee..5714bd1d74 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -13,7 +13,8 @@ Each release is supported for a period of four months, and we aim to create a ne release roughly every two months, accounting for holiday periods, major conferences and other world events. -

      Currently supported releases

      + +## Currently supported releases | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| @@ -117,7 +118,8 @@ branch is actually supported. April 1, 2021 ``` -

      Technical support

      + +### Technical support Technical assistance is offered on a best-effort basis for supported releases only. You can request support from the community on [Kubernetes @@ -128,7 +130,8 @@ Google group. [discussions]: https://github.com/cert-manager/cert-manager/discussions [group]: https://groups.google.com/g/cert-manager-dev -

      Security and bug fixes

      + +### Security and bug fixes We back-port important bug fixes — including security fixes — to all currently supported releases. @@ -137,12 +140,14 @@ currently supported releases. - [Critical bugs](#critical-bugs), - [Long-standing bugs](#long-standing-bugs). -

      Security issues

      + +#### Security issues **Security issues** are fixed as soon as possible. They get back-ported to the last two releases, and a new patch release is immediately created for them. -

      Critical bugs

      + +#### Critical bugs **Critical bugs** include both regression bugs as well as upgrade bugs. @@ -159,7 +164,8 @@ this category. Fixes for critical bugs are (usually) immediately back-ported by creating a new patch release for the currently supported releases. -

      Long-standing bugs

      + +#### Long-standing bugs **Long-standing bug**: sometimes a bug exists for a long time, and may have known workarounds. [#3444][] is an example of a long-standing bug. @@ -168,14 +174,16 @@ Where we feel that back-porting would be difficult or might be a stability risk to clusters running cert-manager, we'll make the fix in a major release but avoid back-porting the fix. -

      Breaking changes

      + +#### Breaking changes Breaking changes are changes that intentionally break the cert-manager Kubernetes API or the command line flags. We avoid making breaking changes where possible, and where they're required we'll give as much notice as possible. -

      Other back-ports

      + +#### Other back-ports We aim to be conservative in what we back-port. That applies especially for anything which could be a _runtime_ change - that is, a change which might alter behavior for someone @@ -201,7 +209,8 @@ Generally we'll seek to be pragmatic. A rule of thumb might be to ask: [#5209]: https://github.com/cert-manager/cert-manager/pull/5209 "release-1.8: rclone" -

      How we determine supported Kubernetes versions

      + +## How we determine supported Kubernetes versions The list of supported Kubernetes versions displayed in the [Supported Releases](#supported-releases) section depends on what the cert-manager maintainers think is reasonable to support and to test. diff --git a/content/docs/troubleshooting/webhook.md b/content/docs/troubleshooting/webhook.md index b842dcb9de..b749a9d40f 100644 --- a/content/docs/troubleshooting/webhook.md +++ b/content/docs/troubleshooting/webhook.md @@ -216,7 +216,6 @@ the webhook deployment, the argument `--healthz-port=6081` was mismatched with the readiness configuration. - ## Error: `i/o timeout` (connectivity issue) > This error message was reported 26 times on Slack. To list these messages, do a search with `in:#cert-manager in:#cert-manager-dev "443: i/o timeout"`. The error message was reported in 2 GitHub issues ([#2811](https://github.com/cert-manager/cert-manager/issues/2811 "i/o timeout from apiserver when connecting to webhook on k3s"), [#4073](https://github.com/cert-manager/cert-manager/issues/4073 "Internal error occurred: failed calling webhook")) @@ -242,7 +241,6 @@ inside the webhook's net namespace, we would see: This issue is caused by the `SYN` packet being dropped somewhere. - ### Cause 1: GKE Private Cluster The default Helm configuration should work with GKE private clusters, but @@ -552,7 +550,6 @@ Error from server (InternalError): error when creating "STDIN": ([1](https://kubernetes.slack.com/archives/C4NV3DWUC/p1632849763397100)). - ## Error: `context deadline exceeded` > This error message was reported in GitHub issues ([2319](https://github.com/cert-manager/cert-manager/issues/2319 "Documenting context deadline exceeded errors relating to the webhook, on bare metal"), [2706](https://github.com/cert-manager/cert-manager/issues/2706 "") [5189](https://github.com/cert-manager/cert-manager/issues/5189 "Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s: context deadline exceeded"), [5004](https://github.com/cert-manager/cert-manager/issues/5004 "After installing cert-manager using kubectl, cmctl check api fails with https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s: context deadline exceeded")), and once [on Stack Overflow](https://stackoverflow.com/questions/72059332/how-can-i-fix-failed-calling-webhook-webhook-cert-manager-io). diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index 17792853bf..a11dcd08b3 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -117,7 +117,8 @@ The `Certificate` will be issued using the issuer named `ca-issuer` in the A full list of the fields supported on the Certificate resource can be found in the [API reference documentation](../reference/api-docs.md#cert-manager.io/v1.CertificateSpec). -

      X.509 key usages and extended key usages

      + +## X.509 key usages and extended key usages cert-manager supports requesting certificates that have a number of [custom key usages](https://tools.ietf.org/html/rfc5280#section-4.2.1.3) and [extended key @@ -134,7 +135,8 @@ certificate does not match the current key usage set. An exhaustive list of supported key usages can be found in the [API reference documentation](../reference/api-docs.md#cert-manager.io/v1.KeyUsage). -

      Temporary Certificates while Issuing

      + +## Temporary Certificates while Issuing When requesting certificates [using the ingress-shim](./ingress.md), the component `ingress-gce`, if used, requires that a temporary certificate is @@ -155,7 +157,8 @@ Adding the following annotation on an ingress will automatically set "issue-temp acme.cert-manager.io/http01-edit-in-place: "true" ``` -

      Rotation of the private key

      + +## Rotation of the private key By default, the private key won't be rotated automatically. Using the setting `rotationPolicy: Always`, the private key Secret associated with a Certificate @@ -201,7 +204,8 @@ spec: rotationPolicy: Always # 🔰 Here. ``` -

      Actions that will trigger a rotation of the private key

      + +### Actions that will trigger a rotation of the private key Setting the `rotationPolicy: Always` won't rotate the private key immediately. In order to rotate the private key, the certificate objects must be reissued. A From 10c7a76f18d74ce9d5dddd6550f0b572866b5a76 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 6 Sep 2023 16:37:19 +0200 Subject: [PATCH 138/264] rename 'Issuing Certificates' to 'Requesting Certificates' Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/configuration/venafi.md | 4 ++-- content/docs/tutorials/venafi/venafi.md | 2 +- content/docs/usage/README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/configuration/venafi.md b/content/docs/configuration/venafi.md index f6f692cb72..29cbbd04c7 100644 --- a/content/docs/configuration/venafi.md +++ b/content/docs/configuration/venafi.md @@ -100,7 +100,7 @@ vaas-issuer True Venafi issuer started 2m You are now ready to issue certificates using the newly provisioned Venafi `Issuer` and Venafi as a Service. -Read the [Issuing Certificates](../usage/certificate.md) document for +Read the [Requesting Certificates](../usage/certificate.md) document for more information on how to create Certificate resources. @@ -256,7 +256,7 @@ $ kubectl describe issuer tpp-issuer --namespace='NAMESPACE OF YOUR ISSUER RESOU You are now ready to issue certificates using the newly provisioned Venafi `Issuer` and Trust Protection Platform. -Read the [Issuing Certificates](../usage/certificate.md) document for +Read the [Requesting Certificates](../usage/certificate.md) document for more information on how to create Certificate resources. ## Issuer specific annotations diff --git a/content/docs/tutorials/venafi/venafi.md b/content/docs/tutorials/venafi/venafi.md index fd7f9dddea..d1a83964b9 100644 --- a/content/docs/tutorials/venafi/venafi.md +++ b/content/docs/tutorials/venafi/venafi.md @@ -384,7 +384,7 @@ correctly, we can begin requesting certificates which can be used by Kubernetes applications. Full information on how to specify and request Certificate resources can be -found in the [Issuing certificates](../../usage/certificate.md) guide. +found in the [Requesting Certificates](../../usage/certificate.md) guide. For now, we will create a basic X.509 Certificate that is valid for our domain, `example.com`: diff --git a/content/docs/usage/README.md b/content/docs/usage/README.md index b0d93801a9..f02af619dc 100644 --- a/content/docs/usage/README.md +++ b/content/docs/usage/README.md @@ -1,5 +1,5 @@ --- -title: Issuing Certificates +title: Requesting Certificates description: 'cert-manager usage: Overview' --- From df3dc765e54d82da6a91fb0b2bd740c49c6665e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Thu, 7 Sep 2023 09:38:15 +0200 Subject: [PATCH 139/264] concept/certificate: add the source of the PNG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I forgot to add the source in [1]. Or rather, I had embedded the source inside the PNG file, which is surprising to anyone who wants to make changes to the diagram. As I explained in [2], let's have the source directly checked in Git. [1]: https://github.com/cert-manager/website/pull/582 [2]: https://github.com/cert-manager/website/issues/426 Signed-off-by: Maël Valais --- public/images/letsencrypt-flow-cert-manager.drawio | 1 + 1 file changed, 1 insertion(+) create mode 100644 public/images/letsencrypt-flow-cert-manager.drawio diff --git a/public/images/letsencrypt-flow-cert-manager.drawio b/public/images/letsencrypt-flow-cert-manager.drawio new file mode 100644 index 0000000000..7febc656cb --- /dev/null +++ b/public/images/letsencrypt-flow-cert-manager.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file From 9637cb3765a21717cfde4615e6856c9628399b2c Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:40:57 +0200 Subject: [PATCH 140/264] update release and support details for 1.13 release Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/installation/supported-releases.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 5714bd1d74..05d9ef2ca7 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -23,9 +23,9 @@ and other world events. ## Upcoming releases -| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:----------------------:|:----------------------:|:----------------------------------:|:---------------------------------:| -| [1.13][] | TBD | ~4 months post release | 1.22 → 1.27 | 4.9 → 4.14 | +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:-------------:|:----------------------:|:----------------------------------:|:---------------------------------:| +| [1.13][] | Sep 12, 2023 | ~4 months post release | 1.23 → 1.28 | 4.10 → 4.14 | The release of cert-manager 1.13 has been delayed while we work to ensure it's the best it can possibly be. We'll have more updates soon! From 49707ad1c2ed97dba097e78831a2e17bd483d313 Mon Sep 17 00:00:00 2001 From: CodingWizKid Date: Tue, 12 Sep 2023 08:47:08 +0200 Subject: [PATCH 141/264] add docs for stackit webhook Signed-off-by: CodingWizKid --- content/docs/configuration/acme/dns01/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/docs/configuration/acme/dns01/README.md b/content/docs/configuration/acme/dns01/README.md index 997a26bedb..d952eef568 100644 --- a/content/docs/configuration/acme/dns01/README.md +++ b/content/docs/configuration/acme/dns01/README.md @@ -181,6 +181,7 @@ Links to these supported providers along with their documentation are below: - [`cert-manager-webhook-netcup`](https://github.com/aellwein/cert-manager-webhook-netcup) - [`cert-manager-webhook-pdns`](https://github.com/zachomedia/cert-manager-webhook-pdns) - [`cert-manager-webhook-zilore`](https://gitlab.com/zilore/cert-manager-webhook-zilore) +- [`stackit-cert-manager-webhook`](https://github.com/stackitcloud/stackit-cert-manager-webhook) You can find more information on how to configure webhook providers [here](./webhook.md). From 655e3550dc93473b51347462122ac60959f4f769 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:18:00 +0200 Subject: [PATCH 142/264] add 1.13 release notes Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/manifest.json | 4 + content/docs/release-notes/README.md | 1 + .../docs/release-notes/release-notes-1.13.md | 129 ++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 content/docs/release-notes/release-notes-1.13.md diff --git a/content/docs/manifest.json b/content/docs/manifest.json index b87e4703f8..8e926ced06 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -562,6 +562,10 @@ "title": "Introduction", "path": "/docs/release-notes/README.md" }, + { + "title": "v1.13", + "path": "/docs/release-notes/release-notes-1.13.md" + }, { "title": "v1.12", "path": "/docs/release-notes/release-notes-1.12.md" diff --git a/content/docs/release-notes/README.md b/content/docs/release-notes/README.md index eccfdd07a1..4a2a1551db 100644 --- a/content/docs/release-notes/README.md +++ b/content/docs/release-notes/README.md @@ -3,6 +3,7 @@ title: Release Notes description: 'cert-manager release notes: Overview' --- +- [`v1.13`](./release-notes-1.13.md) - [`v1.12`](./release-notes-1.12.md) - [`v1.11`](./release-notes-1.11.md) - [`v1.10`](./release-notes-1.10.md) diff --git a/content/docs/release-notes/release-notes-1.13.md b/content/docs/release-notes/release-notes-1.13.md new file mode 100644 index 0000000000..b25657df11 --- /dev/null +++ b/content/docs/release-notes/release-notes-1.13.md @@ -0,0 +1,129 @@ +--- +title: Release 1.13 +description: 'cert-manager release notes: cert-manager 1.13' +--- + +## v1.13.0 + +cert-manager 1.13 brings support for DNS over HTTPS, support for loading options from a versioned +config file for the cert-manager controller, and more. This release also includes the promotion of +the StableCertificateRequestName and SecretsFilteredCaching feature gates to Beta. + +### Major Themes + +#### Load cert-manager controller options from a versioned config file + +It is now possible to load the cert-manager controller options from a versioned config file. +This was supported for the webhook already, but not for the controller. This is very useful +way to better manage these options and it allows us to change the options in the future without +breaking backwards compatibility by introducing a new config file version. + +#### DNS over HTTPS (DoH) support + +It is now possible to use DNS over HTTPS (DoH) for doing the self-checks during the ACME +DNS01 verification. The DNS self-check method to be used is controlled through the command line flag: +`--dns01-recursive-nameservers-only=true` in combination with +`--dns01-recursive-nameservers=https://` (e.g. `https://1.1.1.1/dns-query`) + +This is very useful in case all traffic must be HTTP(S) trafic, eg. when using a HTTPS_PROXY. + +#### StableCertificateRequestName and SecretsFilteredCaching feature gates promoted to Beta + +The StableCertificateRequestName and SecretsFilteredCaching feature gates have been promoted to Beta. +This means that they are enabled by default and that we will not remove them in the future. In case you +are experiencing issues with these features, please let us know. The feature gates can still be disabled +by setting the feature gate to false (eg. in case you are experiencing issues with these features). We +plan to promote these feature gates to GA in the future, which will mean that they can no longer be disabled. + +### Community + +Welcome to these new cert-manager members (more info - https://github.com/cert-manager/cert-manager/pull/6260): +@jsoref +@FlorianLiebhart +@hawksight +@erikgb + +Thanks again to all open-source contributors with commits in this release, including: +@AcidLeroy +@FlorianLiebhart +@lucacome +@cypres +@erikgb +@ubergesundheit +@jkroepke +@jsoref +@gdvalle +@rouke-broersma +@schrodit +@zhangzhiqiangcs +@arukiidou +@hawksight +@Richardds +@kahirokunn + +Thanks also to the following cert-manager maintainers for their contributions during this release: +@SgtCoDFish +@maelvls +@irbekrm +@inteon + +Equally thanks to everyone who provided feedback, helped users and raised issues on Github and Slack and joined our meetings! + +Special thanks to @AcidLeroy for adding "load options from a versioned config file" support for the cert-manager controller! This has been on our wishlist for a very long time. (see https://github.com/cert-manager/cert-manager/pull/5337) + +Also, thanks a lot to @FlorianLiebhart for adding support for DNS over HTTPS for the ACME DNS self-check. This is very useful in case all traffic must be HTTP(S) trafic, eg. when using a HTTPS_PROXY. (see https://github.com/cert-manager/cert-manager/pull/5003) + +Thanks also to the [CNCF](https://www.cncf.io/), which provides resources and support, and to the AWS open source team for being good community members and for their maintenance of the [PrivateCA Issuer](https://github.com/cert-manager/aws-privateca-issuer). + +In addition, massive thanks to [Venafi](https://www.venafi.com/) for contributing developer time and resources towards the continued maintenance of cert-manager projects. + +### Changes + +#### Feature + +- Add support for logging options to webhook config file. (https://github.com/cert-manager/cert-manager/pull/6243, https://github.com/inteon) +- Add view permissions to the well-known (Openshift) user-facing `cluster-reader` aggregated cluster role (https://github.com/cert-manager/cert-manager/pull/6241, https://github.com/erikgb) +- Certificate Shim: distinguish dns names and ip address in certificate (https://github.com/cert-manager/cert-manager/pull/6267, https://github.com/zhangzhiqiangcs) +- Cmctl can now be imported by third parties. (https://github.com/cert-manager/cert-manager/pull/6049, https://github.com/SgtCoDFish) +- Make `enableServiceLinks` configurable for all Deployments and `startupapicheck` Job in Helm chart. (https://github.com/cert-manager/cert-manager/pull/6292, https://github.com/ubergesundheit) +- Promoted the StableCertificateRequestName and SecretsFilteredCaching feature gates to Beta (enabled by default). (https://github.com/cert-manager/cert-manager/pull/6298, https://github.com/inteon) +- The cert-manager controller options are now configurable using a configuration file. (https://github.com/cert-manager/cert-manager/pull/5337, https://github.com/AcidLeroy) +- The pki CertificateTemplate functions now perform validation of the CSR blob, making sure we sign a Certificate that matches the IsCA and (Extended)KeyUsages that are defined in the CertificateRequest resource. (https://github.com/cert-manager/cert-manager/pull/6199, https://github.com/inteon) +- [helm] Add prometheus.servicemonitor.endpointAdditionalProperties to define additional properties on a ServiceMonitor endpoint, e.g. relabelings (https://github.com/cert-manager/cert-manager/pull/6110, https://github.com/jkroepke) + +#### Design + +- DNS over HTTPS (DoH) is now possible for doing the self-checks during the ACME verification. + The DNS check method to be used is controlled through the command line flag: `--dns01-recursive-nameservers-only=true` in combination with `--dns01-recursive-nameservers=https://<` (e.g. `https://8.8.8.8/dns-query`). It keeps using DNS lookup as a default method. (https://github.com/cert-manager/cert-manager/pull/5003, https://github.com/FlorianLiebhart) + +#### Bug or Regression + +- Allow overriding default pdb .minAvailable with .maxUnavailable without setting .minAvailable to null (https://github.com/cert-manager/cert-manager/pull/6087, https://github.com/rouke-broersma) +- BUGFIX: `cmctl check api --wait 0` exited without output and exit code 1; we now make sure we perform the API check at least once and return with the correct error code (https://github.com/cert-manager/cert-manager/pull/6109, https://github.com/inteon) +- BUGFIX: the issuer and certificate-name annotations on a Secret were incorrectly updated when other fields are changed. (https://github.com/cert-manager/cert-manager/pull/6147, https://github.com/inteon) +- BUGFIX[cainjector]: 1-character bug was causing invalid log messages and a memory leak (https://github.com/cert-manager/cert-manager/pull/6232, https://github.com/inteon) +- Fix CloudDNS issuers stuck in propagation check, when multiple instances are issuing for the same FQDN (https://github.com/cert-manager/cert-manager/pull/6088, https://github.com/cypres) +- Fix indentation of Webhook NetworkPolicy matchLabels in helm chart. (https://github.com/cert-manager/cert-manager/pull/6220, https://github.com/ubergesundheit) +- Fixed Cloudflare DNS01 challenge provider race condition when validating multiple domains (https://github.com/cert-manager/cert-manager/pull/6191, https://github.com/Richardds) +- Fixes a bug where webhook was pulling in controller's feature gates. + ⚠️ ⚠️ BREAKING ⚠️ ⚠️ : If you deploy cert-manager using helm and have `.featureGates` value set, the features defined there will no longer be passed to cert-manager webhook, only to cert-manager controller. Use `webhook.featureGates` field instead to define features to be enabled on webhook. + **Potentially breaking**: If you were, for some reason, passing cert-manager controller's features to webhook's `--feature-gates` flag, this will now break (unless the webhook actually has a feature by that name). (https://github.com/cert-manager/cert-manager/pull/6093, https://github.com/irbekrm) +- Fixes an issue where cert-manager would incorrectly reject two IP addresses as being unequal when they should have compared equal. This would be most noticeable when using an IPv6 address which doesn't match how Go's `net.IP.String()` function would have printed that address. (https://github.com/cert-manager/cert-manager/pull/6293, https://github.com/SgtCoDFish) +- We disabled the `enableServiceLinks` option for our ACME http solver pods, because the option caused the pod to be in a crash loop in a cluster with lot of services. (https://github.com/cert-manager/cert-manager/pull/6143, https://github.com/schrodit) +- ⚠️ possibly breaking: Webhook validation of CertificateRequest resources is stricter now: all KeyUsages and ExtendedKeyUsages must be defined directly in the CertificateRequest resource, the encoded CSR can never contain more usages that defined there. (https://github.com/cert-manager/cert-manager/pull/6182, https://github.com/inteon) + +#### Other (Cleanup or Flake) + +- A subset of the klogs flags have been deprecated and will be removed in the future. (https://github.com/cert-manager/cert-manager/pull/5879, https://github.com/maelvls) +- All service links in helm chart deployments have been disabled. (https://github.com/cert-manager/cert-manager/pull/6144, https://github.com/schrodit) +- Cert-manager will now re-issue a certificate if the public key in the latest CertificateRequest resource linked to a Certificate resource does not match the public key of the key encoded in the Secret linked to that Certificate resource (https://github.com/cert-manager/cert-manager/pull/6168, https://github.com/inteon) +- Chore: When hostNetwork is enabled, dnsPolicy is now set to ClusterFirstWithHostNet. (https://github.com/cert-manager/cert-manager/pull/6156, https://github.com/kahirokunn) +- Cleanup the controller configfile structure by introducing sub-structs. (https://github.com/cert-manager/cert-manager/pull/6242, https://github.com/inteon) +- Don't run API Priority and Fairness controller in webhook's extension apiserver (https://github.com/cert-manager/cert-manager/pull/6085, https://github.com/irbekrm) +- Helm: Add apache 2.0 license annotation (https://github.com/cert-manager/cert-manager/pull/6225, https://github.com/arukiidou) +- Make apis/acme/v1/ACMEIssuer.PreferredChain optional in JSON serialization. (https://github.com/cert-manager/cert-manager/pull/6034, https://github.com/gdvalle) +- The SecretPostIssuancePolicyChain now also makes sure that the `cert-manager.io/common-name`, `cert-manager.io/alt-names`, ... annotations on Secrets are kept at their correct value. (https://github.com/cert-manager/cert-manager/pull/6176, https://github.com/inteon) +- The cmctl logging has been improved and support for json logging has been added. (https://github.com/cert-manager/cert-manager/pull/6247, https://github.com/inteon) +- Updates Kubernetes libraries to `v0.27.2`. (https://github.com/cert-manager/cert-manager/pull/6077, https://github.com/lucacome) +- Updates Kubernetes libraries to `v0.27.4`. (https://github.com/cert-manager/cert-manager/pull/6227, https://github.com/lucacome) +- We now only check that the issuer name, kind and group annotations on a Secret match in case those annotations are set. (https://github.com/cert-manager/cert-manager/pull/6152, https://github.com/inteon) From 84b1004bfb401e68102090a86d9291559e93d892 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:45:07 +0200 Subject: [PATCH 143/264] bump cert-manager versions (as described in the release-process) Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/installation/README.md | 2 +- content/docs/installation/code-signing.md | 2 +- content/docs/installation/helm.md | 10 +- content/docs/installation/kubectl.md | 2 +- .../operator-lifecycle-manager.md | 2 +- .../docs/installation/supported-releases.md | 26 +- content/docs/reference/api-docs.md | 806 ++- content/v1.13-docs/README.md | 26 + content/v1.13-docs/cli/README.md | 7 + content/v1.13-docs/cli/acmesolver.md | 17 + content/v1.13-docs/cli/cainjector.md | 45 + content/v1.13-docs/cli/cmctl.md | 30 + content/v1.13-docs/cli/controller.md | 78 + content/v1.13-docs/cli/webhook.md | 51 + content/v1.13-docs/concepts/README.md | 11 + .../concepts/acme-orders-challenges.md | 100 + content/v1.13-docs/concepts/ca-injector.md | 234 + content/v1.13-docs/concepts/certificate.md | 106 + .../v1.13-docs/concepts/certificaterequest.md | 261 + content/v1.13-docs/concepts/issuer.md | 44 + content/v1.13-docs/concepts/webhook.md | 80 + content/v1.13-docs/configuration/README.md | 30 + .../v1.13-docs/configuration/acme/README.md | 391 + .../configuration/acme/dns01/README.md | 188 + .../configuration/acme/dns01/acme-dns.md | 220 + .../configuration/acme/dns01/akamai.md | 86 + .../configuration/acme/dns01/azuredns.md | 505 ++ .../configuration/acme/dns01/cloudflare.md | 108 + .../configuration/acme/dns01/digitalocean.md | 46 + .../configuration/acme/dns01/google.md | 242 + .../configuration/acme/dns01/rfc2136.md | 207 + .../configuration/acme/dns01/route53.md | 259 + .../configuration/acme/dns01/webhook.md | 32 + .../configuration/acme/http01/README.md | 393 + .../acme/http01/externalloadbalancer.md | 34 + content/v1.13-docs/configuration/ca.md | 94 + content/v1.13-docs/configuration/external.md | 51 + .../v1.13-docs/configuration/selfsigned.md | 171 + content/v1.13-docs/configuration/vault.md | 387 + content/v1.13-docs/configuration/venafi.md | 282 + content/v1.13-docs/contributing/README.md | 68 + content/v1.13-docs/contributing/building.md | 267 + .../contributing/coding-conventions.md | 59 + .../contributing/contributing-flow.md | 178 + content/v1.13-docs/contributing/crds.md | 65 + .../v1.13-docs/contributing/dns-providers.md | 23 + content/v1.13-docs/contributing/e2e.md | 148 + .../contributing/external-issuers.md | 77 + .../v1.13-docs/contributing/featuregates.md | 47 + .../google-season-of-docs/2022/README.md | 10 + .../2022/improve-navigation-and-structure.md | 269 + .../google-season-of-docs/README.md | 8 + content/v1.13-docs/contributing/importing.md | 42 + content/v1.13-docs/contributing/kind.md | 31 + content/v1.13-docs/contributing/policy.md | 159 + .../contributing/release-process.md | 735 ++ content/v1.13-docs/contributing/security.md | 13 + content/v1.13-docs/contributing/sign-off.md | 77 + .../v1.13-docs/contributing/signing-keys.md | 72 + .../contributing/third-party-code-donation.md | 79 + content/v1.13-docs/faq/README.md | 180 + content/v1.13-docs/getting-started/README.md | 36 + content/v1.13-docs/installation/README.md | 41 + .../installation/api-compatibility.md | 22 + .../v1.13-docs/installation/best-practice.md | 138 + .../v1.13-docs/installation/code-signing.md | 59 + .../v1.13-docs/installation/compatibility.md | 114 + .../v1.13-docs/installation/featureflags.md | 72 + content/v1.13-docs/installation/helm.md | 288 + content/v1.13-docs/installation/kubectl.md | 125 + .../operator-lifecycle-manager.md | 243 + .../v1.13-docs/installation/other-tools.md | 23 + .../installation/supported-releases.md | 296 + content/v1.13-docs/installation/uninstall.md | 14 + content/v1.13-docs/installation/verify.md | 122 + content/v1.13-docs/manifest.json | 514 ++ content/v1.13-docs/projects/README.md | 31 + .../projects/approver-policy/README.md | 431 ++ .../projects/approver-policy/api-reference.md | 978 +++ .../v1.13-docs/projects/csi-driver-spiffe.md | 257 + content/v1.13-docs/projects/csi-driver.md | 231 + content/v1.13-docs/projects/istio-csr.md | 92 + .../projects/trust-manager/README.md | 417 ++ .../projects/trust-manager/api-reference.md | 592 ++ content/v1.13-docs/reference/README.md | 15 + content/v1.13-docs/reference/api-docs.md | 6380 +++++++++++++++++ content/v1.13-docs/reference/cmctl.md | 344 + .../v1.13-docs/reference/tls-terminology.md | 79 + content/v1.13-docs/troubleshooting/README.md | 116 + content/v1.13-docs/troubleshooting/acme.md | 226 + content/v1.13-docs/troubleshooting/webhook.md | 1068 +++ content/v1.13-docs/tutorials/README.md | 38 + .../tutorials/acme/dns-validation.md | 169 + .../tutorials/acme/example/deployment.yaml | 20 + .../acme/example/ingress-tls-final.yaml | 24 + .../tutorials/acme/example/ingress-tls.yaml | 24 + .../tutorials/acme/example/ingress.yaml | 23 + .../acme/example/pomerium-certificates.yaml | 36 + .../example/pomerium-production-issuer.yaml | 19 + .../acme/example/pomerium-staging-issuer.yaml | 19 + .../acme/example/pomerium-values.yaml | 39 + .../acme/example/production-issuer.yaml | 18 + .../tutorials/acme/example/service.yaml | 11 + .../acme/example/staging-issuer.yaml | 18 + .../tutorials/acme/http-validation.md | 166 + .../acme/migrating-from-kube-lego.md | 232 + .../tutorials/acme/nginx-ingress.md | 602 ++ .../tutorials/acme/pomerium-ingress.md | 191 + content/v1.13-docs/tutorials/backup.md | 167 + .../getting-started-aks-letsencrypt/README.md | 687 ++ .../README.md | 779 ++ .../README.md | 603 ++ .../gatekeeper/deploy-novol.yaml | 29 + .../gatekeeper/deploy-withvol.yaml | 38 + .../gatekeeper-trust-pod-ca-volume.yaml | 29 + .../gatekeeper-trust-pod-ca-volumemount.yaml | 24 + .../trust/bundle-one-ca.yaml | 39 + .../trust/bundle-public.yaml | 13 + .../trust/deploy-auto.yaml | 40 + .../example/example-cluster-issuer.yaml | 46 + .../istio-csr/example/example-issuer.yaml | 45 + .../example/istio-config-getting-started.yaml | 57 + .../tutorials/istio-csr/istio-csr.md | 276 + .../syncing-secrets-across-namespaces.md | 143 + content/v1.13-docs/tutorials/venafi/venafi.md | 585 ++ .../v1.13-docs/tutorials/zerossl/zerossl.md | 180 + content/v1.13-docs/usage/README.md | 31 + content/v1.13-docs/usage/approver-policy.md | 14 + content/v1.13-docs/usage/certificate.md | 372 + content/v1.13-docs/usage/csi.md | 85 + content/v1.13-docs/usage/gateway.md | 438 ++ content/v1.13-docs/usage/ingress.md | 215 + content/v1.13-docs/usage/istio.md | 17 + content/v1.13-docs/usage/kube-csr.md | 166 + .../v1.13-docs/usage/prometheus-metrics.md | 69 + scripts/gendocs/generate-new-import-path-docs | 5 +- 136 files changed, 28359 insertions(+), 119 deletions(-) create mode 100644 content/v1.13-docs/README.md create mode 100644 content/v1.13-docs/cli/README.md create mode 100644 content/v1.13-docs/cli/acmesolver.md create mode 100644 content/v1.13-docs/cli/cainjector.md create mode 100644 content/v1.13-docs/cli/cmctl.md create mode 100644 content/v1.13-docs/cli/controller.md create mode 100644 content/v1.13-docs/cli/webhook.md create mode 100644 content/v1.13-docs/concepts/README.md create mode 100644 content/v1.13-docs/concepts/acme-orders-challenges.md create mode 100644 content/v1.13-docs/concepts/ca-injector.md create mode 100644 content/v1.13-docs/concepts/certificate.md create mode 100644 content/v1.13-docs/concepts/certificaterequest.md create mode 100644 content/v1.13-docs/concepts/issuer.md create mode 100644 content/v1.13-docs/concepts/webhook.md create mode 100644 content/v1.13-docs/configuration/README.md create mode 100644 content/v1.13-docs/configuration/acme/README.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/README.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/acme-dns.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/akamai.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/azuredns.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/cloudflare.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/digitalocean.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/google.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/rfc2136.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/route53.md create mode 100644 content/v1.13-docs/configuration/acme/dns01/webhook.md create mode 100644 content/v1.13-docs/configuration/acme/http01/README.md create mode 100644 content/v1.13-docs/configuration/acme/http01/externalloadbalancer.md create mode 100644 content/v1.13-docs/configuration/ca.md create mode 100644 content/v1.13-docs/configuration/external.md create mode 100644 content/v1.13-docs/configuration/selfsigned.md create mode 100644 content/v1.13-docs/configuration/vault.md create mode 100644 content/v1.13-docs/configuration/venafi.md create mode 100644 content/v1.13-docs/contributing/README.md create mode 100644 content/v1.13-docs/contributing/building.md create mode 100644 content/v1.13-docs/contributing/coding-conventions.md create mode 100644 content/v1.13-docs/contributing/contributing-flow.md create mode 100644 content/v1.13-docs/contributing/crds.md create mode 100644 content/v1.13-docs/contributing/dns-providers.md create mode 100644 content/v1.13-docs/contributing/e2e.md create mode 100644 content/v1.13-docs/contributing/external-issuers.md create mode 100644 content/v1.13-docs/contributing/featuregates.md create mode 100644 content/v1.13-docs/contributing/google-season-of-docs/2022/README.md create mode 100644 content/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md create mode 100644 content/v1.13-docs/contributing/google-season-of-docs/README.md create mode 100644 content/v1.13-docs/contributing/importing.md create mode 100644 content/v1.13-docs/contributing/kind.md create mode 100644 content/v1.13-docs/contributing/policy.md create mode 100644 content/v1.13-docs/contributing/release-process.md create mode 100644 content/v1.13-docs/contributing/security.md create mode 100644 content/v1.13-docs/contributing/sign-off.md create mode 100644 content/v1.13-docs/contributing/signing-keys.md create mode 100644 content/v1.13-docs/contributing/third-party-code-donation.md create mode 100644 content/v1.13-docs/faq/README.md create mode 100644 content/v1.13-docs/getting-started/README.md create mode 100644 content/v1.13-docs/installation/README.md create mode 100644 content/v1.13-docs/installation/api-compatibility.md create mode 100644 content/v1.13-docs/installation/best-practice.md create mode 100644 content/v1.13-docs/installation/code-signing.md create mode 100644 content/v1.13-docs/installation/compatibility.md create mode 100644 content/v1.13-docs/installation/featureflags.md create mode 100644 content/v1.13-docs/installation/helm.md create mode 100644 content/v1.13-docs/installation/kubectl.md create mode 100644 content/v1.13-docs/installation/operator-lifecycle-manager.md create mode 100644 content/v1.13-docs/installation/other-tools.md create mode 100644 content/v1.13-docs/installation/supported-releases.md create mode 100644 content/v1.13-docs/installation/uninstall.md create mode 100644 content/v1.13-docs/installation/verify.md create mode 100644 content/v1.13-docs/manifest.json create mode 100644 content/v1.13-docs/projects/README.md create mode 100644 content/v1.13-docs/projects/approver-policy/README.md create mode 100644 content/v1.13-docs/projects/approver-policy/api-reference.md create mode 100644 content/v1.13-docs/projects/csi-driver-spiffe.md create mode 100644 content/v1.13-docs/projects/csi-driver.md create mode 100644 content/v1.13-docs/projects/istio-csr.md create mode 100644 content/v1.13-docs/projects/trust-manager/README.md create mode 100644 content/v1.13-docs/projects/trust-manager/api-reference.md create mode 100644 content/v1.13-docs/reference/README.md create mode 100644 content/v1.13-docs/reference/api-docs.md create mode 100644 content/v1.13-docs/reference/cmctl.md create mode 100644 content/v1.13-docs/reference/tls-terminology.md create mode 100644 content/v1.13-docs/troubleshooting/README.md create mode 100644 content/v1.13-docs/troubleshooting/acme.md create mode 100644 content/v1.13-docs/troubleshooting/webhook.md create mode 100644 content/v1.13-docs/tutorials/README.md create mode 100644 content/v1.13-docs/tutorials/acme/dns-validation.md create mode 100644 content/v1.13-docs/tutorials/acme/example/deployment.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/ingress-tls-final.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/ingress-tls.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/ingress.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/pomerium-certificates.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/pomerium-production-issuer.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/pomerium-staging-issuer.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/pomerium-values.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/production-issuer.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/service.yaml create mode 100644 content/v1.13-docs/tutorials/acme/example/staging-issuer.yaml create mode 100644 content/v1.13-docs/tutorials/acme/http-validation.md create mode 100644 content/v1.13-docs/tutorials/acme/migrating-from-kube-lego.md create mode 100644 content/v1.13-docs/tutorials/acme/nginx-ingress.md create mode 100644 content/v1.13-docs/tutorials/acme/pomerium-ingress.md create mode 100644 content/v1.13-docs/tutorials/backup.md create mode 100644 content/v1.13-docs/tutorials/getting-started-aks-letsencrypt/README.md create mode 100644 content/v1.13-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md create mode 100644 content/v1.13-docs/tutorials/getting-started-with-trust-manager/README.md create mode 100644 content/v1.13-docs/tutorials/getting-started-with-trust-manager/gatekeeper/deploy-novol.yaml create mode 100644 content/v1.13-docs/tutorials/getting-started-with-trust-manager/gatekeeper/deploy-withvol.yaml create mode 100644 content/v1.13-docs/tutorials/getting-started-with-trust-manager/gatekeeper/gatekeeper-trust-pod-ca-volume.yaml create mode 100644 content/v1.13-docs/tutorials/getting-started-with-trust-manager/gatekeeper/gatekeeper-trust-pod-ca-volumemount.yaml create mode 100644 content/v1.13-docs/tutorials/getting-started-with-trust-manager/trust/bundle-one-ca.yaml create mode 100644 content/v1.13-docs/tutorials/getting-started-with-trust-manager/trust/bundle-public.yaml create mode 100644 content/v1.13-docs/tutorials/getting-started-with-trust-manager/trust/deploy-auto.yaml create mode 100644 content/v1.13-docs/tutorials/istio-csr/example/example-cluster-issuer.yaml create mode 100644 content/v1.13-docs/tutorials/istio-csr/example/example-issuer.yaml create mode 100644 content/v1.13-docs/tutorials/istio-csr/example/istio-config-getting-started.yaml create mode 100644 content/v1.13-docs/tutorials/istio-csr/istio-csr.md create mode 100644 content/v1.13-docs/tutorials/syncing-secrets-across-namespaces.md create mode 100644 content/v1.13-docs/tutorials/venafi/venafi.md create mode 100644 content/v1.13-docs/tutorials/zerossl/zerossl.md create mode 100644 content/v1.13-docs/usage/README.md create mode 100644 content/v1.13-docs/usage/approver-policy.md create mode 100644 content/v1.13-docs/usage/certificate.md create mode 100644 content/v1.13-docs/usage/csi.md create mode 100644 content/v1.13-docs/usage/gateway.md create mode 100644 content/v1.13-docs/usage/ingress.md create mode 100644 content/v1.13-docs/usage/istio.md create mode 100644 content/v1.13-docs/usage/kube-csr.md create mode 100644 content/v1.13-docs/usage/prometheus-metrics.md diff --git a/content/docs/installation/README.md b/content/docs/installation/README.md index 159f0195b0..39fcd350f3 100644 --- a/content/docs/installation/README.md +++ b/content/docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.4/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/docs/installation/code-signing.md b/content/docs/installation/code-signing.md index 32696d151e..fef64be007 100644 --- a/content/docs/installation/code-signing.md +++ b/content/docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.12.4 # change as needed +IMAGE_TAG=v1.13.0 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index 94116a0109..055a1ce82b 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -47,7 +47,7 @@ section below for details on each method. > Recommended for production installations ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.4/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -70,7 +70,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.4 \ + --version v1.13.0 \ # --set installCRDs=true ``` @@ -83,7 +83,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.4 \ + --version v1.13.0 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -114,7 +114,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.12.4 + version: v1.13.0 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -148,7 +148,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.4 \ + --version v1.13.0 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index 380a240646..225b229662 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -19,7 +19,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.4/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index d34cdff2be..eb431286ff 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.12.4 \ +kubectl patch csv cert-manager.v1.13.0 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 05d9ef2ca7..29e5ba4d9a 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -18,16 +18,14 @@ and other world events. | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| -| [1.12][] | May 19, 2023 | Release of 1.14 | 1.22 → 1.27 | 4.9 → 4.14 | -| [1.11][] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.27 | 4.8 → 4.14 | +| [1.13][] | Sep 12, 2023 | Release of 1.15 | 1.23 → 1.28 | 4.10 → 4.15 | +| [1.13.0] | May 19, 2023 | Release of 1.14 | 1.22 → 1.27 | 4.9 → 4.14 | ## Upcoming releases -| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:-------------:|:----------------------:|:----------------------------------:|:---------------------------------:| -| [1.13][] | Sep 12, 2023 | ~4 months post release | 1.23 → 1.28 | 4.10 → 4.14 | - -The release of cert-manager 1.13 has been delayed while we work to ensure it's the best it can possibly be. We'll have more updates soon! +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| +| [1.14][] | Jan 15, 2024 | ~4 months post release | TBD | TBD | Dates in the future are uncertain and might change. @@ -35,6 +33,7 @@ Dates in the future are uncertain and might change. | Release | Release Date | EOL | Compatible Kubernetes versions | Compatible OpenShift versions | |----------|:------------:|:------------:|:------------------------------:|:-----------------------------:| +| [1.11][] | Jan 11, 2023 | Sep 12, 2023 | 1.21 → 1.27 | 4.8 → 4.14 | | [1.10][] | Oct 17, 2022 | May 19, 2023 | 1.20 → 1.26 | 4.7 → 4.13 | | [1.9][] | Jul 22, 2022 | Jan 11, 2023 | 1.20 → 1.24 | 4.7 → 4.11 | | [1.8][] | Apr 05, 2022 | Oct 17, 2022 | 1.19 → 1.24 | 4.6 → 4.11 | @@ -55,7 +54,7 @@ Dates in the future are uncertain and might change. [s]: #kubernetes-supported-versions [1.13]: https://github.com/cert-manager/cert-manager/milestone/34 -[1.12]: https://cert-manager.io/docs/release-notes/release-notes-1.12 +[1.13.0 https://cert-manager.io/docs/release-notes/release-notes-1.12 [1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 [1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 [1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 @@ -221,12 +220,12 @@ including EKS, GKE, AKS and OpenShift. | Vendor | Oldest Kubernetes Release\* | Other Older Kubernetes Releases | |:-----------------:|-----------------------------|------------------------------------------------------------------------------------| -| [EKS][eks] | 1.22 (EOL Jun 2023) | 1.23 (EOL Oct 2023), 1.24 (EOL Jan 2024), 1.25 (EOL May 2024), 1.26 (EOL Jun 2024) | -| [GKE][gke] | 1.23 (EOL Jul 2023) | 1.24 (EOL Oct 2023), 1.25 (EOL Feb 2024), 1.26 (EOL May 2024), 1.27 (EOL Jan 2025) | -| [AKS][aks] | 1.24 (EOL Jul 2023) | 1.25 (EOL Dec 2023), 1.26 (EOL Mar 2024), 1.27 (EOL Jun 2024) | -| [OpenShift 4][os] | 1.22 (4.9, EOL Jun 2023) | 1.23 (4.10, EOL Oct 2023), 1.24 (4.11, EOL Feb 2024), 1.25 (4.12, EOL Jan 2025) | +| [EKS][eks] | 1.23 (EOL Oct 2023) | 1.24 (EOL Jan 2024), 1.25 (EOL May 2024), 1.26 (EOL Jun 2024), 1.28 (EOL Nov 2024) | +| [GKE][gke] | 1.24 (EOL Oct 2023) | 1.25 (EOL Feb 2024), 1.26 (EOL May 2024), 1.27 (EOL Jan 2025), 1.28 (EOL -) | +| [AKS][aks] | 1.25 (EOL Dec 2023) | 1.26 (EOL Mar 2024), 1.27 (EOL Jun 2024), 1.28 (EOL -) | +| [OpenShift 4][os] | 1.23 (4.10, EOL Sep 2023) | 1.24 (4.11, EOL Feb 2024), 1.25 (4.12, EOL Jan 2025), 1.25 (4.13, EOL Nov 2024) | -\*Oldest release relevant to the next cert-manager release, as of 2023-05-19 +\*Oldest release relevant to the next cert-manager release, as of 2023-09-13 [eks]: https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html#kubernetes-release-calendar [gke]: https://cloud.google.com/kubernetes-engine/docs/release-schedule @@ -242,6 +241,7 @@ For convenience, the following table shows these version mappings: | OpenShift versions | Kubernetes version | |--------------------|--------------------| +| 4.15 | 1.28 | | 4.14 | 1.27 | | 4.13 | 1.26 | | 4.12 | 1.25 | diff --git a/content/docs/reference/api-docs.md b/content/docs/reference/api-docs.md index e766b7cfeb..b3d73a0e55 100644 --- a/content/docs/reference/api-docs.md +++ b/content/docs/reference/api-docs.md @@ -13,6 +13,9 @@ description: >-
    • cert-manager.io/v1
    • +
    • + controller.config.cert-manager.io/v1alpha1 +
    • meta.cert-manager.io/v1
    • @@ -2519,7 +2522,7 @@ description: >-

      Certificate

      -

      A Certificate resource should be created to ensure an up to date and signed x509 certificate is stored in the Kubernetes Secret resource named in spec.secretName.

      +

      A Certificate resource should be created to ensure an up to date and signed X.509 certificate is stored in the Kubernetes Secret resource named in spec.secretName.

      The stored certificate will be renewed before it expires (as configured by spec.renewBefore).

      @@ -2559,6 +2562,8 @@ description: >- @@ -2572,7 +2577,11 @@ description: >- + + + + +
      + (Optional) +

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

      Refer to the Kubernetes API documentation for the fields of the metadata field.
      -

      Desired state of the Certificate resource.

      + (Optional) +

      + Specification of the desired state of the Certificate resource. + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status +



      @@ -2586,7 +2595,8 @@ description: >- @@ -2597,7 +2607,8 @@ description: >- @@ -2608,7 +2619,8 @@ description: >- @@ -2621,7 +2633,8 @@ description: >- @@ -2634,10 +2647,9 @@ description: >- @@ -2648,7 +2660,7 @@ description: >- @@ -2659,7 +2671,7 @@ description: >- @@ -2670,7 +2682,7 @@ description: >- @@ -2681,7 +2693,7 @@ description: >- @@ -2691,7 +2703,7 @@ description: >- string @@ -2704,7 +2716,7 @@ description: >- @@ -2717,10 +2729,7 @@ description: >- @@ -2732,7 +2741,8 @@ description: >- @@ -2743,7 +2753,8 @@ description: >- @@ -2756,7 +2767,8 @@ description: >- @@ -2769,7 +2781,7 @@ description: >- @@ -2780,7 +2792,8 @@ description: >- @@ -2791,7 +2804,11 @@ description: >- @@ -2804,9 +2821,10 @@ description: >- @@ -2823,7 +2841,7 @@ description: >- @@ -2831,10 +2849,7 @@ description: >-

      CertificateRequest

      A CertificateRequest is used to request a signed certificate from one of the configured issuers.

      -

      - All fields within the CertificateRequest’s spec are immutable after creation. A CertificateRequest will either succeed or fail, as denoted by its status.state - field. -

      +

      All fields within the CertificateRequest’s spec are immutable after creation. A CertificateRequest will either succeed or fail, as denoted by its Ready status condition and its status.failureTime field.

      A CertificateRequest is a one-shot resource, meaning it represents a single point in time request for a certificate and cannot be re-used.

      (Optional) -

      Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name).

      +

      Requested set of X509 certificate subject attributes. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6

      +

      The common name attribute is specified separately in the commonName field. Cannot be set if the literalSubject field is set.

      (Optional) -

      LiteralSubject is an LDAP formatted string that represents the X.509 Subject field. Use this instead of the Subject field if you need to ensure the correct ordering of the RDN sequence, such as when issuing certs for LDAP authentication. See https://github.com/cert-manager/cert-manager/issues/3203, https://github.com/cert-manager/cert-manager/issues/4424. This field is alpha level and is only supported by cert-manager installations where LiteralCertificateSubject feature gate is enabled on both cert-manager controller and webhook.

      +

      Requested X.509 certificate subject, represented using the LDAP “String Representation of a Distinguished Name” [1]. Important: the LDAP string format also specifies the order of the attributes in the subject, this is important when issuing certs for LDAP authentication. Example: CN=foo,DC=corp,DC=example,DC=com More info [1]: https://datatracker.ietf.org/doc/html/rfc4514 More info: https://github.com/cert-manager/cert-manager/issues/3203 More info: https://github.com/cert-manager/cert-manager/issues/4424

      +

      Cannot be set if the subject or commonName field is set. This is an Alpha Feature and is only enabled with the --feature-gates=LiteralCertificateSubject=true option set on both the controller and webhook components.

      (Optional) -

      CommonName is a common name to be used on the Certificate. The CommonName should have a length of 64 characters or fewer to avoid generating invalid CSRs. This value is ignored by TLS clients when any subject alt name is set. This is x509 behaviour: https://tools.ietf.org/html/rfc6125#section-6.4.4

      +

      Requested common name X509 certificate subject attribute. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 NOTE: TLS clients will ignore this value when any subject alternative name is set (see https://tools.ietf.org/html/rfc6125#section-6.4.4).

      +

      Should have a length of 64 characters or fewer to avoid generating invalid CSRs. Cannot be set if the literalSubject field is set.

      (Optional) -

      The requested ‘duration’ (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. If unset this defaults to 90 days. Certificate will be renewed either 23 through its duration or renewBefore period before its expiry, whichever is later. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration

      +

      Requested ‘duration’ (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute.

      +

      If unset, this defaults to 90 days. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration.

      (Optional) -

      - How long before the currently issued certificate’s expiry cert-manager should renew the certificate. The default is 23 of the issued certificate’s duration. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration - https://golang.org/pkg/time/#ParseDuration -

      +

      How long before the currently issued certificate’s expiry cert-manager should renew the certificate. For example, if a certificate is valid for 60 minutes, and renewBefore=10m, cert-manager will begin to attempt to renew the certificate 50 minutes after it was issued (i.e. when there are 10 minutes remaining until the certificate is no longer valid).

      +

      NOTE: The actual lifetime of the issued certificate is used to determine the renewal time. If an issuer returns a certificate with a different lifetime than the one requested, cert-manager will use the lifetime of the issued certificate.

      +

      If unset, this defaults to 13 of the issued certificate’s lifetime. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration.

      (Optional) -

      DNSNames is a list of DNS subjectAltNames to be set on the Certificate.

      +

      Requested DNS subject alternative names.

      (Optional) -

      IPAddresses is a list of IP address subjectAltNames to be set on the Certificate.

      +

      Requested IP address subject alternative names.

      (Optional) -

      URIs is a list of URI subjectAltNames to be set on the Certificate.

      +

      Requested URI subject alternative names.

      (Optional) -

      EmailAddresses is a list of email subjectAltNames to be set on the Certificate.

      +

      Requested email subject alternative names.

      -

      SecretName is the name of the secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer.

      +

      Name of the Secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer. The Secret resource lives in the same namespace as the Certificate resource.

      (Optional) -

      SecretTemplate defines annotations and labels to be copied to the Certificate’s Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate’s Secret.

      +

      Defines annotations and labels to be copied to the Certificate’s Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate’s Secret.

      (Optional) -

      - Keystores configures additional keystore output formats stored in the - secretName Secret resource. -

      +

      Additional keystore output formats to be stored in the Certificate’s Secret.

      -

      IssuerRef is a reference to the issuer for this certificate. If the kind field is not set, or set to Issuer, an Issuer resource with the given name in the same namespace as the Certificate will be used. If the kind field is set to ClusterIssuer, a ClusterIssuer with the provided name will be used. The name field in this stanza is required at all times.

      +

      Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace.

      +

      The name field of the reference must always be specified.

      (Optional) -

      IsCA will mark this Certificate as valid for certificate signing. This will automatically add the cert sign usage to the list of usages.

      +

      Requested basic constraints isCA value. The isCA value is used to set the isCA field on the created CertificateRequest resources. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute.

      +

      If true, this will automatically add the cert sign usage to the list of requested usages.

      (Optional) -

      Usages is the set of x509 usages that are requested for the certificate. Defaults to digital signature and key encipherment if not specified.

      +

      Requested key usages and extended key usages. These usages are used to set the usages field on the created CertificateRequest resources. If encodeUsagesInRequest is unset or set to true, the usages will additionally be encoded in the request field which contains the CSR blob.

      +

      If unset, defaults to digital signature and key encipherment.

      (Optional) -

      Options to control private keys used for the Certificate.

      +

      Private key options. These include the key algorithm and size, the used encoding and the rotation policy.

      (Optional) -

      EncodeUsagesInRequest controls whether key usages should be present in the CertificateRequest

      +

      Whether the KeyUsage and ExtKeyUsage extensions should be set in the encoded CSR.

      +

      This option defaults to true, and should only be disabled if the target issuer does not support CSRs with these X509 KeyUsage/ ExtKeyUsage extensions.

      (Optional) -

      revisionHistoryLimit is the maximum number of CertificateRequest revisions that are maintained in the Certificate’s history. Each revision represents a single CertificateRequest created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. If set, revisionHistoryLimit must be a value of 1 or greater. If unset (nil), revisions will not be garbage collected. Default value is nil.

      +

      + The maximum number of CertificateRequest revisions that are maintained in the Certificate’s history. Each revision represents a single CertificateRequest + created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. +

      +

      If set, revisionHistoryLimit must be a value of 1 or greater. If unset (nil), revisions will not be garbage collected. Default value is nil.

      (Optional) +

      Defines extra output formats of the private key and signed certificate chain to be written to this Certificate’s target Secret.

      - AdditionalOutputFormats defines extra output formats of the private key and signed certificate chain to be written to this Certificate’s target Secret. This is an Alpha Feature and is only enabled with the - --feature-gates=AdditionalCertificateOutputFormats=true option on both the controller and webhook components. + This is an Alpha Feature and is only enabled with the + --feature-gates=AdditionalCertificateOutputFormats=true option set on both the controller and webhook components.

      (Optional) -

      Status of the Certificate. This is set and managed automatically.

      +

      Status of the Certificate. This is set and managed automatically. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

      @@ -2874,6 +2889,8 @@ description: >- @@ -2887,7 +2904,11 @@ description: >- @@ -5745,6 +6348,19 @@ description: >-

      pprofAddress configures the address on which /debug/pprof endpoint will be served if enabled. Defaults to ‘localhost:6060’.

      + + + +
      + (Optional) +

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

      Refer to the Kubernetes API documentation for the fields of the metadata field.
      -

      Desired state of the CertificateRequest resource.

      + (Optional) +

      + Specification of the desired state of the CertificateRequest resource. + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status +



      @@ -2901,7 +2922,7 @@ description: >- @@ -2913,7 +2934,8 @@ description: >- @@ -2923,7 +2945,8 @@ description: >- []byte @@ -2934,7 +2957,9 @@ description: >- @@ -2947,7 +2972,9 @@ description: >- @@ -3007,7 +3034,7 @@ description: >- @@ -3365,7 +3392,7 @@ description: >- @@ -3467,7 +3494,7 @@ description: >-

      CertificatePrivateKey

      (Appears on: CertificateSpec)

      -

      CertificatePrivateKey contains configuration options for private keys used by the Certificate controller. This allows control of how private keys are rotated.

      +

      CertificatePrivateKey contains configuration options for private keys used by the Certificate controller. These include the key algorithm and size, the used encoding and the rotation policy.

      (Optional) -

      The requested ‘duration’ (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types.

      +

      Requested ‘duration’ (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute.

      -

      IssuerRef is a reference to the issuer for this CertificateRequest. If the kind field is not set, or set to Issuer, an Issuer resource with the given name in the same namespace as the CertificateRequest will be used. If the kind field is set to ClusterIssuer, a ClusterIssuer with the provided name will be used. The name field in this stanza is required at all times. The group field refers to the API group of the issuer which defaults to cert-manager.io if empty.

      +

      Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace.

      +

      The name field of the reference must always be specified.

      -

      The PEM-encoded x509 certificate signing request to be submitted to the CA for signing.

      +

      The PEM-encoded X.509 certificate signing request to be submitted to the issuer for signing.

      +

      If the CSR has a BasicConstraints extension, its isCA attribute must match the isCA value of this CertificateRequest. If the CSR has a KeyUsage extension, its key usages must match the key usages in the usages field of this CertificateRequest. If the CSR has a ExtKeyUsage extension, its extended key usages must match the extended key usages in the usages field of this CertificateRequest.

      (Optional) -

      IsCA will request to mark the certificate as valid for certificate signing when submitting to the issuer. This will automatically add the cert sign usage to the list of usages.

      +

      Requested basic constraints isCA value. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute.

      +

      NOTE: If the CSR in the Request field has a BasicConstraints extension, it must have the same isCA value as specified here.

      +

      If true, this will automatically add the cert sign usage to the list of requested usages.

      (Optional) -

      Usages is the set of x509 usages that are requested for the certificate. If usages are set they SHOULD be encoded inside the CSR spec Defaults to digital signature and key encipherment if not specified.

      +

      Requested key usages and extended key usages.

      +

      NOTE: If the CSR in the Request field has uses the KeyUsage or ExtKeyUsage extension, these extensions must have the same values as specified here without any additional values.

      +

      If unset, defaults to digital signature and key encipherment.

      (Optional) -

      Status of the CertificateRequest. This is set and managed automatically.

      +

      Status of the CertificateRequest. This is set and managed automatically. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

      A condition added to Certificate resources when an issuance is required. This condition will be automatically added and set to true if: * No keypair data exists in the target Secret * The data stored in the Secret cannot be decoded * The private key and certificate do not have matching public keys * If a CertificateRequest for the current revision exists and the certificate data stored in the Secret does not match the - status.certificate on the CertificateRequest. * If no CertificateRequest resource exists for the current revision, the options on the Certificate resource are compared against the x509 data in the Secret, similar to what’s done in earlier versions. If there is a mismatch, an issuance is triggered. This condition may also be added by external API consumers to trigger a re-issuance manually for any other reason. + status.certificate on the CertificateRequest. * If no CertificateRequest resource exists for the current revision, the options on the Certificate resource are compared against the X.509 data in the Secret, similar to what’s done in earlier versions. If there is a mismatch, an issuance is triggered. This condition may also be added by external API consumers to trigger a re-issuance manually for any other reason.

      It will be removed by the ‘issuing’ controller upon completing issuance.

      @@ -3487,7 +3514,8 @@ description: >- @@ -3500,7 +3528,8 @@ description: >- @@ -3513,7 +3542,8 @@ description: >- @@ -3524,7 +3554,8 @@ description: >- @@ -3660,6 +3691,7 @@ description: >-

      (Appears on: CertificateRequest)

      CertificateRequestSpec defines the desired state of CertificateRequest

      +

      NOTE: It is important to note that the issuer can choose to ignore or change any of the requested attributes. How the issuer maps a certificate request to a signed certificate is the full responsibility of the issuer itself. For example, as an edge case, an issuer that inverts the isCA value is free to do so.

      (Optional) -

      RotationPolicy controls how private keys should be regenerated when a re-issuance is being processed. If set to Never, a private key will only be generated if one does not already exist in the target spec.secretName. If one does exists but it does not have the correct algorithm or size, a warning will be raised to await user intervention. If set to Always, a private key matching the specified requirements will be generated whenever a re-issuance occurs. Default is ‘Never’ for backward compatibility.

      +

      RotationPolicy controls how private keys should be regenerated when a re-issuance is being processed.

      +

      If set to Never, a private key will only be generated if one does not already exist in the target spec.secretName. If one does exists but it does not have the correct algorithm or size, a warning will be raised to await user intervention. If set to Always, a private key matching the specified requirements will be generated whenever a re-issuance occurs. Default is Never for backward compatibility.

      (Optional) -

      The private key cryptography standards (PKCS) encoding for this certificate’s private key to be encoded in. If provided, allowed values are PKCS1 and PKCS8 standing for PKCS#1 and PKCS#8, respectively. Defaults to PKCS1 if not specified.

      +

      The private key cryptography standards (PKCS) encoding for this certificate’s private key to be encoded in.

      +

      If provided, allowed values are PKCS1 and PKCS8 standing for PKCS#1 and PKCS#8, respectively. Defaults to PKCS1 if not specified.

      (Optional) -

      Algorithm is the private key algorithm of the corresponding private key for this certificate. If provided, allowed values are either RSA,Ed25519 or ECDSA If algorithm is specified and size is not provided, key size of 256 will be used for ECDSA key algorithm and key size of 2048 will be used for RSA key algorithm. key size is ignored when using the Ed25519 key algorithm.

      +

      Algorithm is the private key algorithm of the corresponding private key for this certificate.

      +

      If provided, allowed values are either RSA, ECDSA or Ed25519. If algorithm is specified and size is not provided, key size of 2048 will be used for RSA key algorithm and key size of 256 will be used for ECDSA key algorithm. key size is ignored when using the Ed25519 key algorithm.

      (Optional) -

      Size is the key bit size of the corresponding private key for this certificate. If algorithm is set to RSA, valid values are 2048, 4096 or 8192, and will default to 2048 if not specified. If algorithm is set to ECDSA, valid values are 256, 384 or 521, and will default to 256 if not specified. If algorithm is set to Ed25519, Size is ignored. No other values are allowed.

      +

      Size is the key bit size of the corresponding private key for this certificate.

      +

      If algorithm is set to RSA, valid values are 2048, 4096 or 8192, and will default to 2048 if not specified. If algorithm is set to ECDSA, valid values are 256, 384 or 521, and will default to 256 if not specified. If algorithm is set to Ed25519, Size is ignored. No other values are allowed.

      @@ -3679,7 +3711,7 @@ description: >- @@ -3691,7 +3723,8 @@ description: >- @@ -3701,7 +3734,8 @@ description: >- []byte @@ -3712,7 +3746,9 @@ description: >- @@ -3725,7 +3761,9 @@ description: >- @@ -3797,7 +3835,7 @@ description: >- @@ -3809,7 +3847,7 @@ description: >- @@ -3822,7 +3860,7 @@ description: >- @@ -3880,7 +3918,9 @@ description: >-

      CertificateSpec

      (Appears on: Certificate)

      -

      CertificateSpec defines the desired state of Certificate. A valid Certificate requires at least one of a CommonName, DNSName, or URISAN to be valid.

      +

      CertificateSpec defines the desired state of Certificate.

      +

      NOTE: The specification contains a lot of “requested” certificate attributes, it is important to note that the issuer can choose to ignore or change any of these requested attributes. How the issuer maps a certificate request to a signed certificate is the full responsibility of the issuer itself. For example, as an edge case, an issuer that inverts the isCA value is free to do so.

      +

      A valid Certificate requires at least one of a CommonName, LiteralSubject, DNSName, or URI to be valid.

      (Optional) -

      The requested ‘duration’ (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types.

      +

      Requested ‘duration’ (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute.

      -

      IssuerRef is a reference to the issuer for this CertificateRequest. If the kind field is not set, or set to Issuer, an Issuer resource with the given name in the same namespace as the CertificateRequest will be used. If the kind field is set to ClusterIssuer, a ClusterIssuer with the provided name will be used. The name field in this stanza is required at all times. The group field refers to the API group of the issuer which defaults to cert-manager.io if empty.

      +

      Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace.

      +

      The name field of the reference must always be specified.

      -

      The PEM-encoded x509 certificate signing request to be submitted to the CA for signing.

      +

      The PEM-encoded X.509 certificate signing request to be submitted to the issuer for signing.

      +

      If the CSR has a BasicConstraints extension, its isCA attribute must match the isCA value of this CertificateRequest. If the CSR has a KeyUsage extension, its key usages must match the key usages in the usages field of this CertificateRequest. If the CSR has a ExtKeyUsage extension, its extended key usages must match the extended key usages in the usages field of this CertificateRequest.

      (Optional) -

      IsCA will request to mark the certificate as valid for certificate signing when submitting to the issuer. This will automatically add the cert sign usage to the list of usages.

      +

      Requested basic constraints isCA value. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute.

      +

      NOTE: If the CSR in the Request field has a BasicConstraints extension, it must have the same isCA value as specified here.

      +

      If true, this will automatically add the cert sign usage to the list of requested usages.

      (Optional) -

      Usages is the set of x509 usages that are requested for the certificate. If usages are set they SHOULD be encoded inside the CSR spec Defaults to digital signature and key encipherment if not specified.

      +

      Requested key usages and extended key usages.

      +

      NOTE: If the CSR in the Request field has uses the KeyUsage or ExtKeyUsage extension, these extensions must have the same values as specified here without any additional values.

      +

      If unset, defaults to digital signature and key encipherment.

      (Optional) -

      List of status conditions to indicate the status of a CertificateRequest. Known condition types are Ready and InvalidRequest.

      +

      List of status conditions to indicate the status of a CertificateRequest. Known condition types are Ready, InvalidRequest, Approved and Denied.

      (Optional)

      - The PEM encoded x509 certificate resulting from the certificate signing request. If not set, the CertificateRequest has either not been completed or has failed. More information on failure can be found by checking the + The PEM encoded X.509 certificate resulting from the certificate signing request. If not set, the CertificateRequest has either not been completed or has failed. More information on failure can be found by checking the conditions field.

      (Optional) -

      The PEM encoded x509 certificate of the signer, also known as the CA (Certificate Authority). This is set on a best-effort basis by different issuers. If not set, the CA is assumed to be unknown/not available.

      +

      The PEM encoded X.509 certificate of the signer, also known as the CA (Certificate Authority). This is set on a best-effort basis by different issuers. If not set, the CA is assumed to be unknown/not available.

      @@ -3900,7 +3940,8 @@ description: >- @@ -3911,7 +3952,8 @@ description: >- @@ -3922,7 +3964,8 @@ description: >- @@ -3935,7 +3978,8 @@ description: >- @@ -3948,10 +3992,9 @@ description: >- @@ -3962,7 +4005,7 @@ description: >- @@ -3973,7 +4016,7 @@ description: >- @@ -3984,7 +4027,7 @@ description: >- @@ -3995,7 +4038,7 @@ description: >- @@ -4005,7 +4048,7 @@ description: >- string @@ -4018,7 +4061,7 @@ description: >- @@ -4031,10 +4074,7 @@ description: >- @@ -4046,7 +4086,8 @@ description: >- @@ -4057,7 +4098,8 @@ description: >- @@ -4070,7 +4112,8 @@ description: >- @@ -4083,7 +4126,7 @@ description: >- @@ -4094,7 +4137,8 @@ description: >- @@ -4105,7 +4149,11 @@ description: >- @@ -4118,9 +4166,10 @@ description: >- @@ -4175,7 +4224,7 @@ description: >- @@ -4763,7 +4812,7 @@ description: >-

      "ECDSA"

      @@ -4771,7 +4820,7 @@ description: >-

      "Ed25519"

      @@ -4779,7 +4828,7 @@ description: >-

      "RSA"

      @@ -4800,7 +4849,7 @@ description: >-

      "PKCS1"

      @@ -4808,10 +4857,7 @@ description: >-

      "PKCS8"

      @@ -5358,6 +5404,563 @@ description: >-
      (Optional) -

      Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name).

      +

      Requested set of X509 certificate subject attributes. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6

      +

      The common name attribute is specified separately in the commonName field. Cannot be set if the literalSubject field is set.

      (Optional) -

      LiteralSubject is an LDAP formatted string that represents the X.509 Subject field. Use this instead of the Subject field if you need to ensure the correct ordering of the RDN sequence, such as when issuing certs for LDAP authentication. See https://github.com/cert-manager/cert-manager/issues/3203, https://github.com/cert-manager/cert-manager/issues/4424. This field is alpha level and is only supported by cert-manager installations where LiteralCertificateSubject feature gate is enabled on both cert-manager controller and webhook.

      +

      Requested X.509 certificate subject, represented using the LDAP “String Representation of a Distinguished Name” [1]. Important: the LDAP string format also specifies the order of the attributes in the subject, this is important when issuing certs for LDAP authentication. Example: CN=foo,DC=corp,DC=example,DC=com More info [1]: https://datatracker.ietf.org/doc/html/rfc4514 More info: https://github.com/cert-manager/cert-manager/issues/3203 More info: https://github.com/cert-manager/cert-manager/issues/4424

      +

      Cannot be set if the subject or commonName field is set. This is an Alpha Feature and is only enabled with the --feature-gates=LiteralCertificateSubject=true option set on both the controller and webhook components.

      (Optional) -

      CommonName is a common name to be used on the Certificate. The CommonName should have a length of 64 characters or fewer to avoid generating invalid CSRs. This value is ignored by TLS clients when any subject alt name is set. This is x509 behaviour: https://tools.ietf.org/html/rfc6125#section-6.4.4

      +

      Requested common name X509 certificate subject attribute. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 NOTE: TLS clients will ignore this value when any subject alternative name is set (see https://tools.ietf.org/html/rfc6125#section-6.4.4).

      +

      Should have a length of 64 characters or fewer to avoid generating invalid CSRs. Cannot be set if the literalSubject field is set.

      (Optional) -

      The requested ‘duration’ (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. If unset this defaults to 90 days. Certificate will be renewed either 23 through its duration or renewBefore period before its expiry, whichever is later. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration

      +

      Requested ‘duration’ (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute.

      +

      If unset, this defaults to 90 days. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration.

      (Optional) -

      - How long before the currently issued certificate’s expiry cert-manager should renew the certificate. The default is 23 of the issued certificate’s duration. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration - https://golang.org/pkg/time/#ParseDuration -

      +

      How long before the currently issued certificate’s expiry cert-manager should renew the certificate. For example, if a certificate is valid for 60 minutes, and renewBefore=10m, cert-manager will begin to attempt to renew the certificate 50 minutes after it was issued (i.e. when there are 10 minutes remaining until the certificate is no longer valid).

      +

      NOTE: The actual lifetime of the issued certificate is used to determine the renewal time. If an issuer returns a certificate with a different lifetime than the one requested, cert-manager will use the lifetime of the issued certificate.

      +

      If unset, this defaults to 13 of the issued certificate’s lifetime. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration.

      (Optional) -

      DNSNames is a list of DNS subjectAltNames to be set on the Certificate.

      +

      Requested DNS subject alternative names.

      (Optional) -

      IPAddresses is a list of IP address subjectAltNames to be set on the Certificate.

      +

      Requested IP address subject alternative names.

      (Optional) -

      URIs is a list of URI subjectAltNames to be set on the Certificate.

      +

      Requested URI subject alternative names.

      (Optional) -

      EmailAddresses is a list of email subjectAltNames to be set on the Certificate.

      +

      Requested email subject alternative names.

      -

      SecretName is the name of the secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer.

      +

      Name of the Secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer. The Secret resource lives in the same namespace as the Certificate resource.

      (Optional) -

      SecretTemplate defines annotations and labels to be copied to the Certificate’s Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate’s Secret.

      +

      Defines annotations and labels to be copied to the Certificate’s Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate’s Secret.

      (Optional) -

      - Keystores configures additional keystore output formats stored in the - secretName Secret resource. -

      +

      Additional keystore output formats to be stored in the Certificate’s Secret.

      -

      IssuerRef is a reference to the issuer for this certificate. If the kind field is not set, or set to Issuer, an Issuer resource with the given name in the same namespace as the Certificate will be used. If the kind field is set to ClusterIssuer, a ClusterIssuer with the provided name will be used. The name field in this stanza is required at all times.

      +

      Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace.

      +

      The name field of the reference must always be specified.

      (Optional) -

      IsCA will mark this Certificate as valid for certificate signing. This will automatically add the cert sign usage to the list of usages.

      +

      Requested basic constraints isCA value. The isCA value is used to set the isCA field on the created CertificateRequest resources. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute.

      +

      If true, this will automatically add the cert sign usage to the list of requested usages.

      (Optional) -

      Usages is the set of x509 usages that are requested for the certificate. Defaults to digital signature and key encipherment if not specified.

      +

      Requested key usages and extended key usages. These usages are used to set the usages field on the created CertificateRequest resources. If encodeUsagesInRequest is unset or set to true, the usages will additionally be encoded in the request field which contains the CSR blob.

      +

      If unset, defaults to digital signature and key encipherment.

      (Optional) -

      Options to control private keys used for the Certificate.

      +

      Private key options. These include the key algorithm and size, the used encoding and the rotation policy.

      (Optional) -

      EncodeUsagesInRequest controls whether key usages should be present in the CertificateRequest

      +

      Whether the KeyUsage and ExtKeyUsage extensions should be set in the encoded CSR.

      +

      This option defaults to true, and should only be disabled if the target issuer does not support CSRs with these X509 KeyUsage/ ExtKeyUsage extensions.

      (Optional) -

      revisionHistoryLimit is the maximum number of CertificateRequest revisions that are maintained in the Certificate’s history. Each revision represents a single CertificateRequest created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. If set, revisionHistoryLimit must be a value of 1 or greater. If unset (nil), revisions will not be garbage collected. Default value is nil.

      +

      + The maximum number of CertificateRequest revisions that are maintained in the Certificate’s history. Each revision represents a single CertificateRequest + created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. +

      +

      If set, revisionHistoryLimit must be a value of 1 or greater. If unset (nil), revisions will not be garbage collected. Default value is nil.

      (Optional) +

      Defines extra output formats of the private key and signed certificate chain to be written to this Certificate’s target Secret.

      - AdditionalOutputFormats defines extra output formats of the private key and signed certificate chain to be written to this Certificate’s target Secret. This is an Alpha Feature and is only enabled with the - --feature-gates=AdditionalCertificateOutputFormats=true option on both the controller and webhook components. + This is an Alpha Feature and is only enabled with the + --feature-gates=AdditionalCertificateOutputFormats=true option set on both the controller and webhook components.

      (Optional) -

      The time after which the certificate stored in the secret named by this resource in spec.secretName is valid.

      +

      The time after which the certificate stored in the secret named by this resource in spec.secretName is valid.

      -

      Denotes the ECDSA private key type.

      +

      ECDSA private key algorithm.

      -

      Denotes the Ed25519 private key type.

      +

      Ed25519 private key algorithm.

      -

      Denotes the RSA private key type.

      +

      RSA private key algorithm.

      -

      PKCS1 key encoding will produce PEM files that include the type of private key as part of the PEM header, e.g. BEGIN RSA PRIVATE KEY. If the keyAlgorithm is set to ‘ECDSA’, this will produce private keys that use the BEGIN EC PRIVATE KEY header.

      +

      PKCS1 private key encoding. PKCS1 produces a PEM block that contains the private key algorithm in the header and the private key in the body. A key that uses this can be recognised by its BEGIN RSA PRIVATE KEY or BEGIN EC PRIVATE KEY header. NOTE: This encoding is not supported for Ed25519 keys. Attempting to use this encoding with an Ed25519 key will be ignored and default to PKCS8.

      -

      - PKCS8 key encoding will produce PEM files with the BEGIN PRIVATE KEY - header. It encodes the keyAlgorithm of the private key as part of the DER encoded PEM block. -

      +

      PKCS8 private key encoding. PKCS8 produces a PEM block with a static header and both the private key algorithm and the private key in the body. A key that uses this encoding can be recognised by its BEGIN PRIVATE KEY header.


      +

      controller.config.cert-manager.io/v1alpha1

      +
      +

      Package v1alpha1 is the v1alpha1 version of the controller config API.

      +
      +

      Resource Types:

      +
        +

        ACMEDNS01Config

        +

        (Appears on: ControllerConfiguration)

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + recursiveNameservers +
        + []string +
        +

        Each nameserver can be either the IP address and port of a standard recursive DNS server, or the endpoint to an RFC 8484 DNS over HTTPS endpoint. For example, the following values are valid: - “8.8.8.8:53” (Standard DNS) - “https://1.1.1.1/dns-query” (DNS over HTTPS)

        +
        + recursiveNameserversOnly +
        + bool +
        +

        When true, cert-manager will only ever query the configured DNS resolvers to perform the ACME DNS01 self check. This is useful in DNS constrained environments, where access to authoritative nameservers is restricted. Enabling this option could cause the DNS01 self check to take longer due to caching performed by the recursive nameservers.

        +
        + checkRetryPeriod +
        + time.Duration +
        +

        The duration the controller should wait between a propagation check. Despite the name, this flag is used to configure the wait period for both DNS01 and HTTP01 challenge propagation checks. For DNS01 challenges the propagation check verifies that a TXT record with the challenge token has been created. For HTTP01 challenges the propagation check verifies that the challenge token is served at the challenge URL. This should be a valid duration string, for example 180s or 1h

        +
        +

        ACMEHTTP01Config

        +

        (Appears on: ControllerConfiguration)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + solverImage +
        + string +
        +

        The Docker image to use to solve ACME HTTP01 challenges. You most likely will not need to change this parameter unless you are testing a new feature or developing cert-manager.

        +
        + solverResourceRequestCPU +
        + string +
        +

        Defines the resource request CPU size when spawning new ACME HTTP01 challenge solver pods.

        +
        + solverResourceRequestMemory +
        + string +
        +

        Defines the resource request Memory size when spawning new ACME HTTP01 challenge solver pods.

        +
        + solverResourceLimitsCPU +
        + string +
        +

        Defines the resource limits CPU size when spawning new ACME HTTP01 challenge solver pods.

        +
        + solverResourceLimitsMemory +
        + string +
        +

        Defines the resource limits Memory size when spawning new ACME HTTP01 challenge solver pods.

        +
        + solverRunAsNonRoot +
        + bool +
        +

        Defines the ability to run the http01 solver as root for troubleshooting issues

        +
        + solverNameservers +
        + []string +
        +

        A list of comma separated dns server endpoints used for ACME HTTP01 check requests. This should be a list containing host and port, for example [“8.8.8.8:53”,“8.8.4.4:53”] Allows specifying a list of custom nameservers to perform HTTP01 checks on.

        +
        +

        ControllerConfiguration

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + kubeConfig +
        + string +
        +

        kubeConfig is the kubeconfig file used to connect to the Kubernetes apiserver. If not specified, the webhook will attempt to load the in-cluster-config.

        +
        + apiServerHost +
        + string +
        +

        apiServerHost is used to override the API server connection address. Deprecated: use kubeConfig instead.

        +
        + kubernetesAPIQPS +
        + float32 +
        +

        Indicates the maximum queries-per-second requests to the Kubernetes apiserver TODO: floats are not recommended. Maybe we should use resource.Quantity? https://kubernetes.io/docs/reference/kubernetes-api/common-definitions/quantity/

        +
        + kubernetesAPIBurst +
        + int32 +
        +

        The maximum burst queries-per-second of requests sent to the Kubernetes apiserver

        +
        + namespace +
        + string +
        +

        If set, this limits the scope of cert-manager to a single namespace and ClusterIssuers are disabled. If not specified, all namespaces will be watched”

        +
        + clusterResourceNamespace +
        + string +
        +

        Namespace to store resources owned by cluster scoped resources such as ClusterIssuer in.

        +
        + leaderElectionConfig +
        + + LeaderElectionConfig + +
        +

        LeaderElectionConfig configures the behaviour of the leader election

        +
        + controllers +
        + []string +
        +

        A list of controllers to enable. [’’] enables all controllers, [‘foo’] enables only the foo controller [’’, ‘-foo’] disables the controller named foo.

        +
        + issuerAmbientCredentials +
        + bool +
        +

        Whether an issuer may make use of ambient credentials. ‘Ambient Credentials’ are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the Issuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata.

        +
        + clusterIssuerAmbientCredentials +
        + bool +
        +

        Whether a cluster-issuer may make use of ambient credentials for issuers. ‘Ambient Credentials’ are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the ClusterIssuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata.

        +
        + enableCertificateOwnerRef +
        + bool +
        +

        Whether to set the certificate resource as an owner of secret where the tls certificate is stored. When this flag is enabled, the secret will be automatically removed when the certificate resource is deleted.

        +
        + copiedAnnotationPrefixes +
        + []string +
        +

        Specify which annotations should/shouldn’t be copied from Certificate to CertificateRequest and Order, as well as from CertificateSigningRequest to Order, by passing a list of annotation key prefixes. A prefix starting with a dash(-) specifies an annotation that shouldn’t be copied. Example: ‘*,-kubectl.kuberenetes.io/’- all annotations will be copied apart from the ones where the key is prefixed with ‘kubectl.kubernetes.io/’.

        +
        + numberOfConcurrentWorkers +
        + int32 +
        +

        The number of concurrent workers for each controller.

        +
        + maxConcurrentChallenges +
        + int32 +
        +

        The maximum number of challenges that can be scheduled as ‘processing’ at once.

        +
        + metricsListenAddress +
        + string +
        +

        The host and port that the metrics endpoint should listen on.

        +
        + healthzListenAddress +
        + string +
        +

        The host and port address, separated by a ‘:’, that the healthz server should listen on.

        +
        + enablePprof +
        + bool +
        +

        Enable profiling for controller.

        +
        + pprofAddress +
        + string +
        +

        The host and port that Go profiler should listen on, i.e localhost:6060. Ensure that profiler is not exposed on a public address. Profiler will be served at /debug/pprof.

        +
        + logging +
        + k8s.io/component-base/logs/api/v1.LoggingConfiguration +
        +

        + logging configures the logging behaviour of the controller. + https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration +

        +
        + featureGates +
        + map[string]bool +
        + (Optional) +

        featureGates is a map of feature names to bools that enable or disable experimental features. Default: nil

        +
        + ingressShimConfig +
        + + IngressShimConfig + +
        +

        ingressShimConfig configures the behaviour of the ingress-shim controller

        +
        + acmeHTTP01Config +
        + + ACMEHTTP01Config + +
        +

        acmeHTTP01Config configures the behaviour of the ACME HTTP01 challenge solver

        +
        + acmeDNS01Config +
        + + ACMEDNS01Config + +
        +

        acmeDNS01Config configures the behaviour of the ACME DNS01 challenge solver

        +
        +

        IngressShimConfig

        +

        (Appears on: ControllerConfiguration)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + defaultIssuerName +
        + string +
        +

        Default issuer/certificates details consumed by ingress-shim Name of the Issuer to use when the tls is requested but issuer name is not specified on the ingress resource.

        +
        + defaultIssuerKind +
        + string +
        +

        Kind of the Issuer to use when the TLS is requested but issuer kind is not specified on the ingress resource.

        +
        + defaultIssuerGroup +
        + string +
        +

        Group of the Issuer to use when the TLS is requested but issuer group is not specified on the ingress resource.

        +
        + defaultAutoCertificateAnnotations +
        + []string +
        +

        The annotation consumed by the ingress-shim controller to indicate a ingress is requesting a certificate

        +
        +

        KubeConfig

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + path +
        + string +
        +

        Path to a kubeconfig. Only required if out-of-cluster.

        +
        + currentContext +
        + bool +
        + (Optional) +

        If true, use the current context from the kubeconfig file. If false, use the context specified by ControllerConfiguration.Context. Default: true

        +
        + context +
        + string +
        + (Optional) +

        The kubeconfig context to use. Default: current-context from kubeconfig file

        +
        +

        LeaderElectionConfig

        +

        (Appears on: ControllerConfiguration)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + enabled +
        + bool +
        +

        If true, cert-manager will perform leader election between instances to ensure no more than one instance of cert-manager operates at a time

        +
        + namespace +
        + string +
        +

        Namespace used to perform leader election. Only used if leader election is enabled

        +
        + leaseDuration +
        + time.Duration +
        +

        The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled.

        +
        + renewDeadline +
        + time.Duration +
        +

        The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled.

        +
        + retryPeriod +
        + time.Duration +
        +

        The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled.

        +
        + healthzTimeout +
        + time.Duration +
        +

        Leader election healthz checks within this timeout period after the lease expires will still return healthy.

        +
        +

        meta.cert-manager.io/v1

        Package v1 contains meta types for cert-manager APIs

        @@ -5677,20 +6280,20 @@ description: >-
        securePort
        - int + int32
        -

        securePort is the port number to listen on for secure TLS connections from the kube-apiserver. Defaults to 6443.

        +

        securePort is the port number to listen on for secure TLS connections from the kube-apiserver. If 0, a random available port will be chosen. Defaults to 6443.

        healthzPort
        - int + int32
        -

        healthzPort is the port number to listen on (using plaintext HTTP) for healthz connections. Defaults to 6080.

        +

        healthzPort is the port number to listen on (using plaintext HTTP) for healthz connections. If 0, a random available port will be chosen. Defaults to 6080.

        + logging +
        + k8s.io/component-base/logs/api/v1.LoggingConfiguration +
        +

        + logging configures the logging behaviour of the webhook. + https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration +

        +
        featureGates @@ -5760,5 +6376,5 @@ description: >-

        - Generated with gen-crd-api-reference-docs on git commit fe41951. + Generated with gen-crd-api-reference-docs on git commit d34bd7a.

        diff --git a/content/v1.13-docs/README.md b/content/v1.13-docs/README.md new file mode 100644 index 0000000000..c53f1bc99f --- /dev/null +++ b/content/v1.13-docs/README.md @@ -0,0 +1,26 @@ +--- +title: cert-manager +description: cert-manager documentation homepage +--- + +cert-manager adds certificates and certificate issuers as resource types in +Kubernetes clusters, and simplifies the process of obtaining, renewing and +using those certificates. + +It can issue certificates from a variety of supported sources, including +[Let's Encrypt](https://letsencrypt.org), [HashiCorp Vault](https://www.vaultproject.io), +and [Venafi](https://www.venafi.com/) as well as private PKI. + +It will ensure certificates are valid and up to date, and attempt to +renew certificates at a configured time before expiry. + +It is loosely based upon the work of +[kube-lego](https://github.com/jetstack/kube-lego) and has borrowed some +wisdom from other similar projects such as +[kube-cert-manager](https://github.com/PalmStoneGames/kube-cert-manager). + +![High level overview diagram explaining cert-manager architecture](/images/high-level-overview.svg) + +This website provides the full technical documentation for the project, and can be +used as a reference; if you feel that there's anything missing, please let us know +or [raise a PR](https://github.com/cert-manager/website/pulls) to add it. diff --git a/content/v1.13-docs/cli/README.md b/content/v1.13-docs/cli/README.md new file mode 100644 index 0000000000..0d1a516335 --- /dev/null +++ b/content/v1.13-docs/cli/README.md @@ -0,0 +1,7 @@ +--- +title: CLI reference +description: cert-manager CLI documentation +--- + +View the `--help` output from our various CLI tools, including those which run in containers in your cluster. +This might help if you need to tweak an option or if you need to check which values are valid! \ No newline at end of file diff --git a/content/v1.13-docs/cli/acmesolver.md b/content/v1.13-docs/cli/acmesolver.md new file mode 100644 index 0000000000..baee31aff4 --- /dev/null +++ b/content/v1.13-docs/cli/acmesolver.md @@ -0,0 +1,17 @@ +--- +title: acmesolver CLI reference +description: "cert-manager acmesolver CLI documentation" +--- +``` +HTTP server used to solve ACME challenges. + +Usage: + acmesolver [flags] + +Flags: + --domain string the domain name to verify + -h, --help help for acmesolver + --key string the challenge key to respond with + --listen-port int the port number to listen on for connections (default 8089) + --token string the challenge token to verify against +``` diff --git a/content/v1.13-docs/cli/cainjector.md b/content/v1.13-docs/cli/cainjector.md new file mode 100644 index 0000000000..0bcdf50014 --- /dev/null +++ b/content/v1.13-docs/cli/cainjector.md @@ -0,0 +1,45 @@ +--- +title: cainjector CLI reference +description: "cert-manager cainjector CLI documentation" +--- +``` + +cert-manager CA injector is a Kubernetes addon to automate the injection of CA data into +webhooks and APIServices from cert-manager certificates. + +It will ensure that annotated webhooks and API services always have the correct +CA data from the referenced certificates, which can then be used to serve API +servers and webhook servers. + +Usage: + ca-injector [flags] + +Flags: + --add_dir_header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --enable-profiling Enable profiling for cainjector + --feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are: + AllAlpha=true|false (ALPHA - default=false) + AllBeta=true|false (BETA - default=false) + -h, --help help for ca-injector + --kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster. + --leader-elect If true, cainjector will perform leader election between instances to ensure no more than one instance of cainjector operates at a time (default true) + --leader-election-lease-duration duration The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled. (default 1m0s) + --leader-election-namespace string Namespace used to perform leader election. Only used if leader election is enabled (default "kube-system") + --leader-election-renew-deadline duration The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled. (default 40s) + --leader-election-retry-period duration The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled. (default 15s) + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log_file string If non-empty, use this log file (no effect when -logtostderr=true) + --log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --namespace string If set, this limits the scope of cainjector to a single namespace. If set, cainjector will not update resources with certificates outside of the configured namespace. + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --profiler-address string Address of the Go profiler (pprof) if enabled. This should never be exposed on a public interface. (default "localhost:6060") + --skip_headers If true, avoid header prefixes in the log messages + --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` diff --git a/content/v1.13-docs/cli/cmctl.md b/content/v1.13-docs/cli/cmctl.md new file mode 100644 index 0000000000..dff83b3bc2 --- /dev/null +++ b/content/v1.13-docs/cli/cmctl.md @@ -0,0 +1,30 @@ +--- +title: cmctl CLI reference +description: "cert-manager cmctl CLI documentation" +--- +``` + +cmctl is a CLI tool manage and configure cert-manager resources for Kubernetes + +Usage: cmctl [command] + +Available Commands: + approve Approve a CertificateRequest + check Check cert-manager components + convert Convert cert-manager config files between different API versions + create Create cert-manager resources + deny Deny a CertificateRequest + experimental Interact with experimental features + help Help about any command + inspect Get details on certificate related resources + renew Mark a Certificate for manual renewal + status Get details on current status of cert-manager resources + upgrade Tools that assist in upgrading cert-manager + version Print the cert-manager CLI version and the deployed cert-manager version + +Flags: + -h, --help help for cmctl + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + +Use "cmctl [command] --help" for more information about a command. +``` diff --git a/content/v1.13-docs/cli/controller.md b/content/v1.13-docs/cli/controller.md new file mode 100644 index 0000000000..768ffb05cc --- /dev/null +++ b/content/v1.13-docs/cli/controller.md @@ -0,0 +1,78 @@ +--- +title: controller CLI reference +description: "cert-manager controller CLI documentation" +--- +``` + +cert-manager is a Kubernetes addon to automate the management and issuance of +TLS certificates from various issuing sources. + +It will ensure certificates are valid and up to date periodically, and attempt +to renew certificates at an appropriate time before expiry. + +Usage: + cert-manager-controller [flags] + +Flags: + --acme-http01-solver-image string The docker image to use to solve ACME HTTP01 challenges. You most likely will not need to change this parameter unless you are testing a new feature or developing cert-manager. (default "quay.io/jetstack/cert-manager-acmesolver:canary") + --acme-http01-solver-nameservers strings A list of comma separated dns server endpoints used for ACME HTTP01 check requests. This should be a list containing host and port, for example 8.8.8.8:53,8.8.4.4:53 + --acme-http01-solver-resource-limits-cpu string Defines the resource limits CPU size when spawning new ACME HTTP01 challenge solver pods. (default "100m") + --acme-http01-solver-resource-limits-memory string Defines the resource limits Memory size when spawning new ACME HTTP01 challenge solver pods. (default "64Mi") + --acme-http01-solver-resource-request-cpu string Defines the resource request CPU size when spawning new ACME HTTP01 challenge solver pods. (default "10m") + --acme-http01-solver-resource-request-memory string Defines the resource request Memory size when spawning new ACME HTTP01 challenge solver pods. (default "64Mi") + --acme-http01-solver-run-as-non-root Defines the ability to run the http01 solver as root for troubleshooting issues (default true) + --add_dir_header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --auto-certificate-annotations strings The annotation consumed by the ingress-shim controller to indicate a ingress is requesting a certificate (default [kubernetes.io/tls-acme]) + --cluster-issuer-ambient-credentials Whether a cluster-issuer may make use of ambient credentials for issuers. 'Ambient Credentials' are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the ClusterIssuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata. (default true) + --cluster-resource-namespace string Namespace to store resources owned by cluster scoped resources such as ClusterIssuer in. This must be specified if ClusterIssuers are enabled. (default "kube-system") + --controllers strings A list of controllers to enable. '--controllers=*' enables all on-by-default controllers, '--controllers=foo' enables just the controller named 'foo', '--controllers=*,-foo' disables the controller named 'foo'. + All controllers: issuers, clusterissuers, certificates-metrics, ingress-shim, gateway-shim, orders, challenges, certificaterequests-issuer-acme, certificaterequests-approver, certificaterequests-issuer-ca, certificaterequests-issuer-selfsigned, certificaterequests-issuer-vault, certificaterequests-issuer-venafi, certificates-trigger, certificates-issuing, certificates-key-manager, certificates-request-manager, certificates-readiness, certificates-revision-manager (default [*]) + --copied-annotation-prefixes strings Specify which annotations should/shouldn't be copiedfrom Certificate to CertificateRequest and Order, as well as from CertificateSigningRequest to Order, by passing a list of annotation key prefixes.A prefix starting with a dash(-) specifies an annotation that shouldn't be copied. Example: '*,-kubectl.kuberenetes.io/'- all annotationswill be copied apart from the ones where the key is prefixed with 'kubectl.kubernetes.io/'. (default [*,-kubectl.kubernetes.io/,-fluxcd.io/,-argocd.argoproj.io/]) + --default-issuer-group string Group of the Issuer to use when the tls is requested but issuer group is not specified on the ingress resource. (default "cert-manager.io") + --default-issuer-kind string Kind of the Issuer to use when the tls is requested but issuer kind is not specified on the ingress resource. (default "Issuer") + --default-issuer-name string Name of the Issuer to use when the tls is requested but issuer name is not specified on the ingress resource. + --dns01-check-retry-period duration The duration the controller should wait between a propagation check. Despite the name, this flag is used to configure the wait period for both DNS01 and HTTP01 challenge propagation checks. For DNS01 challenges the propagation check verifies that a TXT record with the challenge token has been created. For HTTP01 challenges the propagation check verifies that the challenge token is served at the challenge URL.This should be a valid duration string, for example 180s or 1h (default 10s) + --dns01-recursive-nameservers strings A list of comma separated dns server endpoints used for DNS01 check requests. This should be a list containing host and port, for example 8.8.8.8:53,8.8.4.4:53 + --dns01-recursive-nameservers-only When true, cert-manager will only ever query the configured DNS resolvers to perform the ACME DNS01 self check. This is useful in DNS constrained environments, where access to authoritative nameservers is restricted. Enabling this option could cause the DNS01 self check to take longer due to caching performed by the recursive nameservers. + --enable-certificate-owner-ref Whether to set the certificate resource as an owner of secret where the tls certificate is stored. When this flag is enabled, the secret will be automatically removed when the certificate resource is deleted. + --enable-profiling Enable profiling for controller. + --feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are: + AdditionalCertificateOutputFormats=true|false (ALPHA - default=false) + AllAlpha=true|false (ALPHA - default=false) + AllBeta=true|false (BETA - default=false) + ExperimentalCertificateSigningRequestControllers=true|false (ALPHA - default=false) + ExperimentalGatewayAPISupport=true|false (ALPHA - default=false) + LiteralCertificateSubject=true|false (ALPHA - default=false) + ServerSideApply=true|false (ALPHA - default=false) + StableCertificateRequestName=true|false (ALPHA - default=false) + UseCertificateRequestBasicConstraints=true|false (ALPHA - default=false) + ValidateCAA=true|false (ALPHA - default=false) + -h, --help help for cert-manager-controller + --issuer-ambient-credentials Whether an issuer may make use of ambient credentials. 'Ambient Credentials' are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the Issuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata. + --kube-api-burst int the maximum burst queries-per-second of requests sent to the Kubernetes apiserver (default 50) + --kube-api-qps float32 indicates the maximum queries-per-second requests to the Kubernetes apiserver (default 20) + --kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster. + --leader-elect If true, cert-manager will perform leader election between instances to ensure no more than one instance of cert-manager operates at a time (default true) + --leader-election-lease-duration duration The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled. (default 1m0s) + --leader-election-namespace string Namespace used to perform leader election. Only used if leader election is enabled (default "kube-system") + --leader-election-renew-deadline duration The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled. (default 40s) + --leader-election-retry-period duration The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled. (default 15s) + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log_file string If non-empty, use this log file (no effect when -logtostderr=true) + --log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --master string Optional apiserver host address to connect to. If not specified, autoconfiguration will be attempted. + --max-concurrent-challenges int The maximum number of challenges that can be scheduled as 'processing' at once. (default 60) + --metrics-listen-address string The host and port that the metrics endpoint should listen on. (default "0.0.0.0:9402") + --namespace string If set, this limits the scope of cert-manager to a single namespace and ClusterIssuers are disabled. If not specified, all namespaces will be watched + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --profiler-address string The host and port that Go profiler should listen on, i.e localhost:6060. Ensure that profiler is not exposed on a public address. Profiler will be served at /debug/pprof. (default "localhost:6060") + --skip_headers If true, avoid header prefixes in the log messages + --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` diff --git a/content/v1.13-docs/cli/webhook.md b/content/v1.13-docs/cli/webhook.md new file mode 100644 index 0000000000..f4d6ef30c3 --- /dev/null +++ b/content/v1.13-docs/cli/webhook.md @@ -0,0 +1,51 @@ +--- +title: webhook CLI reference +description: "cert-manager webhook CLI documentation" +--- +``` +Webhook component providing API validation, mutation and conversion functionality for cert-manager (canary) () + +Usage: + webhook [flags] + +Flags: + --add-dir-header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --api-server-host string Optional apiserver host address to connect to. If not specified, autoconfiguration will be attempted. + --config string Path to a file containing a WebhookConfiguration object used to configure the webhook + --dynamic-serving-ca-secret-name string name of the secret used to store the CA that signs serving certificates certificates + --dynamic-serving-ca-secret-namespace string namespace of the secret used to store the CA that signs serving certificates + --dynamic-serving-dns-names strings DNS names that should be present on certificates generated by the dynamic serving CA + --enable-profiling Enable profiling for webhook. + --feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are: + AdditionalCertificateOutputFormats=true|false (ALPHA - default=false) + AllAlpha=true|false (ALPHA - default=false) + AllBeta=true|false (BETA - default=false) + ExperimentalCertificateSigningRequestControllers=true|false (ALPHA - default=false) + ExperimentalGatewayAPISupport=true|false (ALPHA - default=false) + LiteralCertificateSubject=true|false (ALPHA - default=false) + ServerSideApply=true|false (ALPHA - default=false) + StableCertificateRequestName=true|false (ALPHA - default=false) + UseCertificateRequestBasicConstraints=true|false (ALPHA - default=false) + ValidateCAA=true|false (ALPHA - default=false) + --healthz-port int port number to listen on for insecure healthz connections (default 6080) + -h, --help help for webhook + --kubeconfig string optional path to the kubeconfig used to connect to the apiserver. If not specified, in-cluster-config will be used + --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log-dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) + --log-file string If non-empty, use this log file (no effect when -logtostderr=true) + --log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --profiler-address string Address of the Go profiler (pprof). This should never be exposed on a public interface. If this flag is not set, the profiler is not run. (default "localhost:6060") + --secure-port int port number to listen on for secure TLS connections (default 6443) + --skip-headers If true, avoid header prefixes in the log messages + --skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + --tls-cert-file string path to the file containing the TLS certificate to serve with + --tls-cipher-suites strings Comma-separated list of cipher suites for the server. If omitted, the default Go cipher suites will be use. Possible values: TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_RC4_128_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_RC4_128_SHA + --tls-min-version string Minimum TLS version supported. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13 + --tls-private-key-file string path to the file containing the TLS private key to serve with + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` diff --git a/content/v1.13-docs/concepts/README.md b/content/v1.13-docs/concepts/README.md new file mode 100644 index 0000000000..48ed7e2377 --- /dev/null +++ b/content/v1.13-docs/concepts/README.md @@ -0,0 +1,11 @@ +--- +title: Concepts +description: cert-manager core concepts +--- + +There are several components and ideas that make up cert-manager. This section +describes them on a conceptual level, to aid with understanding how cert-manager +does its job. + +You probably don't want this section if you're just getting started; check out +a [tutorial](../tutorials/README.md) instead. \ No newline at end of file diff --git a/content/v1.13-docs/concepts/acme-orders-challenges.md b/content/v1.13-docs/concepts/acme-orders-challenges.md new file mode 100644 index 0000000000..80c31606e4 --- /dev/null +++ b/content/v1.13-docs/concepts/acme-orders-challenges.md @@ -0,0 +1,100 @@ +--- +title: ACME Orders and Challenges +description: 'cert-manager core concepts: ACME Orders and Challenges' +--- + +cert-manager supports requesting certificates from ACME servers, including from +[Let's Encrypt](https://letsencrypt.org/), with use of the [ACME +Issuer](../configuration/acme/README.md). These certificates are typically trusted on +the public Internet by most computers. To successfully request a certificate, +cert-manager must solve ACME Challenges which are completed in order to prove +that the client owns the DNS addresses that are being requested. + +In order to complete these challenges, cert-manager introduces two +`CustomResource` types; `Orders` and `Challenges`. + +## Orders + +`Order` resources are used by the ACME issuer to manage the lifecycle of an ACME +'order' for a signed TLS certificate. More details on ACME orders and domain +validation can be found on the Let's Encrypt website +[here](https://letsencrypt.org/how-it-works/). An order represents a single +certificate request which will be created automatically once a new +[`CertificateRequest`](./certificaterequest.md) resource referencing an ACME +issuer has been created. `CertificateRequest` resources are created +automatically by cert-manager once a [`Certificate`](./certificate.md) resource +is created, has its specification changed, or needs renewal. + +As an end-user, you will never need to manually create an `Order` resource. +Once created, an `Order` cannot be changed. Instead, a new `Order` resource must +be created. + +The `Order` resource encapsulates multiple ACME 'challenges' for that 'order', +and as such, will manage one or more `Challenge` resources. + +## Challenges + +`Challenge` resources are used by the ACME issuer to manage the lifecycle of an +ACME 'challenge' that must be completed in order to complete an 'authorization' +for a single DNS name/identifier. + +When an `Order` resource is created, the order controller will create +`Challenge` resources for each DNS name that is being authorized with the ACME +server. + +As an end-user, you will never need to manually create a `Challenge` resource. +Once created, a `Challenge` cannot be changed. Instead, a new `Challenge` +resource must be created. + +### Challenge Lifecycle + +After a `Challenge` resource has been created, it will be initially queued for +processing. Processing will not begin until the challenge has been 'scheduled' +to start. This scheduling process prevents too many challenges being attempted +at once, or multiple challenges for the same DNS name being attempted at once. +For more information on how challenges are scheduled, read the [challenge +scheduling](#challenge-scheduling). + +Once a challenge has been scheduled, it will first be 'synced' with the ACME +server in order to determine its current state. If the challenge is already +valid, its 'state' will be updated to 'valid', and will also set +`status.processing = false` to 'unschedule' itself. + +If the challenge is still 'pending', the challenge controller will 'present' the +challenge using the configured solver, one of HTTP01 or DNS01. Once the +challenge has been 'presented', it will set `status.presented = true`. + +Once 'presented', the challenge controller will perform a 'self check' to +ensure that the challenge has 'propagated' (i.e. the authoritative DNS servers +have been updated to respond correctly, or the changes to the ingress resources +have been observed and in-use by the ingress controller). + +If the self check fails, cert-manager will retry the self check with a fixed 10 +second retry interval. Challenges that do not ever complete the self check will +continue retrying until the user intervenes by either retrying the `Order` (by +deleting the `Order` resource) or amending the associated `Certificate` resource +to resolve any configuration errors. + +Once the self check is passing, the ACME 'authorization' associated with this +challenge will be 'accepted'. + +The final state of the authorization after accepting it will be copied across to +the Challenge's `status.state` field, as well as the 'error reason' if an error +occurred whilst the ACME server attempted to validate the challenge. + +Once a Challenge has entered the `valid`, `invalid`, `expired` or `revoked` +state, it will set `status.processing = false` to prevent any further processing +of the ACME challenge, and to allow another challenge to be scheduled if there +is a backlog of challenges to complete. + +### Challenge Scheduling + +Instead of attempting to process all challenges at once, challenges are +'scheduled' by cert-manager. + +This scheduler applies a cap on the maximum number of simultaneous challenges +as well as disallows two challenges for the same DNS name and solver type +(`HTTP01` or `DNS01`) to be completed at once. + +The maximum number of challenges that can be processed at a time is 60 as of +[`ddff78`](https://github.com/cert-manager/cert-manager/blob/ddff78f011558e64186d61f7c693edced1496afa/pkg/controller/acmechallenges/scheduler/scheduler.go#L31-L33). \ No newline at end of file diff --git a/content/v1.13-docs/concepts/ca-injector.md b/content/v1.13-docs/concepts/ca-injector.md new file mode 100644 index 0000000000..2c7c8dd638 --- /dev/null +++ b/content/v1.13-docs/concepts/ca-injector.md @@ -0,0 +1,234 @@ +--- +title: CA Injector +description: 'cert-manager core concepts: CA Injector' +--- + +`cainjector` helps to configure the CA certificates for: +[Mutating Webhooks], +[Validating Webhooks] +[Conversion Webhooks] and [API Services] + +In particular, `cainjector` populates the `caBundle` field of four API types: +`ValidatingWebhookConfiguration`, +`MutatingWebhookConfiguration` +`CustomResourceDefinition` and `APIService`. +The first three resource types are used to configure how the Kubernetes API server connects to webhooks. +This `caBundle` data is loaded by the Kubernetes API server and used to verify the serving certificates of webhook API servers. +`APIService` is used to represent an [Extension API Server]. `caBundle` of `APIService` can be populated with CA cert that can be used to validate the API server's serving certificate. + +We will refer to these four API types as *injectable* resources. + + +An *injectable* resource MUST have one of these annotations: +`cert-manager.io/inject-ca-from`, +`cert-manager.io/inject-ca-from-secret`, or +`cert-manager.io/inject-apiserver-ca`, depending on the injection *source*. +This is explained in more detail below. + +`cainjector` copies CA data from one of three *sources*: +a Kubernetes `Secret`, +a cert-manager `Certificate`, or from +the Kubernetes API server CA certificate (which `cainjector` itself uses to verify its TLS connection to the Kubernetes API server). + +If the *source* is a Kubernetes `Secret`, that resource MUST also have an `cert-manager.io/allow-direct-injection: "true"` annotation. +The three *source* types are explained in more detail below. + + +## Examples + +Here are examples demonstrating how to use the three `cainjector` *sources*. +In each case we use `ValidatingWebhookConfiguration` as the *injectable*, +but you can substitute `MutatingWebhookConfiguration` or `CustomResourceDefinition` definition instead. + +### Injecting CA data from a Certificate resource + +Here is an example of a `ValidatingWebhookConfiguration` +configured with the annotation `cert-manager.io/inject-ca-from`, +which will make `cainjector` populate the `caBundle` field using CA data from a cert-manager `Certificate`. + +NOTE: This example does not deploy a webhook server, +it only deploys a partial webhook configuration, +but it should be sufficient to help you understand what `cainjector` does: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: example1 + +--- + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: webhook1 + annotations: + cert-manager.io/inject-ca-from: example1/webhook1-certificate +webhooks: +- name: webhook1.example.com + admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook1 + namespace: example1 + path: /validate + port: 443 + sideEffects: None + +--- + +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: webhook1-certificate + namespace: example1 +spec: + secretName: webhook1-certificate + dnsNames: + - webhook1.example1 + issuerRef: + name: selfsigned + +--- + +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned + namespace: example1 +spec: + selfSigned: {} +``` + +You should find that the `caBundle` value is now identical to the CA value in the `Secret` for the `Certificate`: + +``` +kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io webhook1 -o yaml | grep caBundle +kubectl -n example1 get secret webhook1-certificate -o yaml | grep ca.crt +``` + +And after a short time, the Kubernetes API server will read that new `caBundle` value and use it to verify a TLS connection to the webhook server. + +### Injecting CA data from a Secret resource + +Here is another example of a `ValidatingWebhookConfiguration` +this time configured with the annotation `cert-manager.io/inject-ca-from-secret`, +which will make `cainjector` populate the `caBundle` field using CA data from a Kubernetes `Secret`. + +NOTE: This example does not deploy a webhook server, +it only deploys a partial webhook configuration, +but it should be sufficient to help you understand what `cainjector` does: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: example2 + +--- + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: webhook2 + annotations: + cert-manager.io/inject-ca-from-secret: example2/example-ca +webhooks: +- name: webhook2.example.com + admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook2 + namespace: example2 + path: /validate + port: 443 + sideEffects: None + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: example-ca + namespace: example2 + annotations: + cert-manager.io/allow-direct-injection: "true" +type: kubernetes.io/tls +data: + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5akNDQWQ2Z0F3SUJBZ0lRTkdJZ24yM3BQYVpNbk9MUjJnVmZHakFOQmdrcWhraUc5dzBCQVFzRkFEQVYKTVJNd0VRWURWUVFERXdwRmVHRnRjR3hsSUVOQk1CNFhEVEl3TURreU5ERTFOREEwTVZvWERUSXdNVEl5TXpFMQpOREEwTVZvd0ZURVRNQkVHQTFVRUF4TUtSWGhoYlhCc1pTQkRRVENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFECmdnRVBBRENDQVFvQ2dnRUJBS2F3RzVoMzlreHdyNEl0WCtHaDNYVWQrdTVJc2ZlSFdoTTc4TTRQTmZFeXhQMXoKRmNLN1d0MHJFMkwwNUppYmQ4ZjNpb3k5OXNnQ3I4OEw2SWxYZTB0RnkzNysxenJ4TFluR2hDQnZzZjltd0hLbgpIVTEvNERwQjROZkhPbFllNE9tbHVoNE9HdmZINU1EbDh5OWZGMjhXRXVBQ2dwdmpCUWxvRDNlVjJ5UmJvQ2kyCmtSTDJWYTFZL0FQZEpWK21VYkFvZmg0bllmUmNLRTJsSUg0RG5ZdXFPU3JaaituZUQ2M2RTSktxcHQ5K2luN2YKNHljZ2pQYU93MmdyKzhLK291QTlSQTV1VDI3SVNJcUJDcEV6elRqbVBUUWNvUTYxZGF0aDZkc1lsTEU4aWZWUwp4RWZuVEdQKy94M0FXQXR4eU5lanVuZGFXbVNFL3h5OHh0K0FxblVDQXdFQUFhTkNNRUF3RGdZRFZSMFBBUUgvCkJBUURBZ0trTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkowNkc5eEc2V1VBTHB6T3JYaHAKV2dsTm5qMkFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUI3ZG9CZnBLR3o4VlRQSnc0YXhpdisybzJpMHE1SQpSRzU2UE81WnhKQktZQlRROElHQmFOSm1yeGtmNTJCV0ttUGp4cXlNSGRwWjVBU00zOUJkZVUzRGtEWHp4RkgwCjM5RU12UnhIUERyMGQ4cTFFbndQT0xZY1hzNjJhYjdidE11cTJUMFNNZzRYMkY5VmNKTW5YdjlrNnA0VGZNR3MKVThCQnJhVGhUZm53ejBsWXMyblFjdzNmZjZ1bG1wWlk4K3BTak1aVDNJZHZOMFA4Y2hOdUlmUFRHWDJmSlo2NQpxcUUrelRoU3hIeXFTOTVoczhsd1lRRUhGQlVsalRnMCtQZThXL0hOSXZBOU9TYWw1U3UvdlhydmcxN04xdHVyCk5XcWRyZU5OVm1ubXMvTFJodmthWTBGblRvbFNBRkNXWS9GSDY5ZzRPcThiMHVyK3JVMHZOZFFXCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: "" + tls.crt: "" +``` + +You should find that the `caBundle` value is now identical to the `ca.crt` value in the `Secret`: + +``` +kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io webhook2 -o yaml | grep caBundle +``` + +And after a short time, the Kubernetes API server will read that new `caBundle` value and use it to verify a TLS connection to the webhook server. + +This `Secret` based injection mechanism can operate independently of the `Certificate` based mechanism described earlier. +It will work without the cert-manager CRDs installed +and it will work if the cert-manager CRDs and associated webhook servers are not yet configured. + +NOTE: For this reason, cert-manager uses the `Secret` based injection mechanism to bootstrap its own webhook server. +The cert-manager webhook server generates its own private key and self-signed certificate and places them in a `Secret` when it starts up. + +### Injecting the Kubernetes API Server CA + +Here is another example of a `ValidatingWebhookConfiguration` +this time configured with the annotation `cert-manager.io/inject-apiserver-ca: "true"`, +which will make `cainjector` populate the `caBundle` field using the same CA certificate used by the Kubernetes API server. + +NOTE: This example does not deploy a webhook server, +it only deploys a partial webhook configuration, +but it should be sufficient to help you understand what `cainjector` does: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: example3 + +--- + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: webhook3 + annotations: + cert-manager.io/inject-apiserver-ca: "true" +webhooks: +- name: webhook3.example.com + admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook3 + namespace: example3 + path: /validate + port: 443 + sideEffects: None + +``` + +You should find that the `caBundle` value is now identical to the CA used in your `KubeConfig` file: + +``` +kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io webhook3 -o yaml | grep caBundle +kubectl config view --minify --raw | grep certificate-authority-data +``` + +And after a short time, the Kubernetes API server will read that new `caBundle` value and use it to verify a TLS connection to the webhook server. + +NOTE: In this case you will have to ensure that your webhook is configured to serve a TLS certificate that has been signed by the Kubernetes cluster CA. +The disadvantages of this mechanism are that: you will require access to the private key of the Kubernetes cluster CA and you will need to manually rotate the webhook certificate. + +[Validating Webhooks]: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook +[Mutating Webhooks]: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook +[Conversion Webhooks]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion +[API Services]: https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/api-service-v1/ +[Extension API Server]: https://kubernetes.io/docs/tasks/extend-kubernetes/setup-extension-api-server/ \ No newline at end of file diff --git a/content/v1.13-docs/concepts/certificate.md b/content/v1.13-docs/concepts/certificate.md new file mode 100644 index 0000000000..3d8ca12198 --- /dev/null +++ b/content/v1.13-docs/concepts/certificate.md @@ -0,0 +1,106 @@ +--- +title: Certificate +description: 'cert-manager core concepts: Certificates' +--- + +cert-manager has the concept of `Certificates` that define a desired X.509 +certificate which will be renewed and kept up to date. A `Certificate` is a +namespaced resource that references an `Issuer` or `ClusterIssuer` that +determine what will be honoring the certificate request. + +When a `Certificate` is created, a corresponding `CertificateRequest` resource +is created by cert-manager containing the encoded X.509 certificate request, +`Issuer` reference, and other options based upon the specification of the +`Certificate` resource. + +Here is one such example of a `Certificate` resource. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: acme-crt +spec: + secretName: acme-crt-secret + dnsNames: + - example.com + - foo.example.com + issuerRef: + name: letsencrypt-prod + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: Issuer + group: cert-manager.io +``` + +This `Certificate` will tell cert-manager to attempt to use the `Issuer` named +`letsencrypt-prod` to obtain a certificate key pair for the `example.com` and +`foo.example.com` domains. If successful, the resulting TLS key and certificate +will be stored in a secret named `acme-crt-secret`, with keys of `tls.key`, and +`tls.crt` respectively. This secret will live in the same namespace as the +`Certificate` resource. + +When a certificate is issued by an intermediate CA and the `Issuer` can provide +the issued certificate's chain, the contents of `tls.crt` will be the requested +certificate followed by the certificate chain. + +Additionally, if the Certificate Authority is known, the corresponding CA +certificate will be stored in the secret with key `ca.crt`. For example, with +the ACME issuer, the CA is not known and `ca.crt` will not exist in +`acme-crt-secret`. + +cert-manager intentionally avoids adding root certificates to `tls.crt`, because they +are useless in a situation where TLS is being done securely. For more information, +see [RFC 5246 section 7.4.2](https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.2) +which contains the following explanation: + +> Because certificate validation requires that root keys be distributed +> independently, the self-signed certificate that specifies the root +> certificate authority MAY be omitted from the chain, under the +> assumption that the remote end must already possess it in order to +> validate it in any case. + +
        + +When configuring a client to connect to a TLS server with a serving certificate that is signed by a private CA, +you will need to provide the client with the CA certificate in order for it to verify the server. +`ca.crt` will likely contain the certificate you need to trust, +but __do not mount the same `Secret` as the server__ to access `ca.crt`. +This is because: + +1. That `Secret` also contains the private key of the server, which should only be accessible to the server. + You should use RBAC to ensure that the `Secret` containing the serving certificate and private key are only accessible to Pods that need it. +2. Rotating CA certificates safely relies on being able to have both the old and new CA certificates trusted at the same time. + By consuming the CA directly from the source, this isn't possible; + you'll be _forced_ to have some down-time in order to rotate certificates. + +When configuring the client you should independently choose and fetch the CA certificates that you want to trust. +Download the CA out of band and store it in a `Secret` or `ConfigMap` separate from the `Secret` containing the server's private key and certificate. + +This ensures that if the material in the `Secret` containing the server key and certificate is tampered with, +the client will fail to connect to the compromised server. + +The same concept also applies when configuring a server for mutually-authenticated TLS; +don't give the server access to Secret containing the client certificate and private key. + +
        + +The `dnsNames` field specifies a list of [`Subject Alternative +Names`](https://en.wikipedia.org/wiki/Subject_Alternative_Name) to be associated +with the certificate. + +The referenced `Issuer` must exist in the same namespace as the `Certificate`. +A `Certificate` can alternatively reference a `ClusterIssuer` which is +non-namespaced and so can be referenced from any namespace. + +You can read more on how to configure your `Certificate` resources +[here](../usage/certificate.md). + +## Certificate Lifecycle + +This diagram shows the lifecycle of a Certificate named `cert-1` using an +ACME / Let's Encrypt issuer. You don't need to understand all of these steps +to use cert-manager; this is more of an explanation of the logic which happens +under the hood for those curious about the process. + +![Life of a Certificate](/images/letsencrypt-flow-cert-manager.png) \ No newline at end of file diff --git a/content/v1.13-docs/concepts/certificaterequest.md b/content/v1.13-docs/concepts/certificaterequest.md new file mode 100644 index 0000000000..5dbc1ca546 --- /dev/null +++ b/content/v1.13-docs/concepts/certificaterequest.md @@ -0,0 +1,261 @@ +--- +title: CertificateRequest +description: 'cert-manager core concepts: CertificateRequests' +--- + +The `CertificateRequest` is a namespaced resource in cert-manager that is used +to request X.509 certificates from an [`Issuer`](./issuer.md). The resource +contains a base64 encoded string of a PEM encoded certificate request which is +sent to the referenced issuer. A successful issuance will return a signed +certificate, based on the certificate signing request. `CertificateRequests` are +typically consumed and managed by controllers or other systems and should not be +used by humans - unless specifically needed. + +A simple `CertificateRequest` looks like the following: + +```yaml +apiVersion: cert-manager.io/v1 +kind: CertificateRequest +metadata: + name: my-ca-cr +spec: + request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQzNqQ0NBY1lDQVFBd2daZ3hDekFKQmdOVkJBWVRBbHBhTVE4d0RRWURWUVFJREFaQmNHOXNiRzh4RFRBTApCZ05WQkFjTUJFMXZiMjR4RVRBUEJnTlZCQW9NQ0VwbGRITjBZV05yTVJVd0V3WURWUVFMREF4alpYSjBMVzFoCmJtRm5aWEl4RVRBUEJnTlZCQU1NQ0dwdmMyaDJZVzVzTVN3d0tnWUpLb1pJaHZjTkFRa0JGaDFxYjNOb2RXRXUKZG1GdWJHVmxkWGRsYmtCcVpYUnpkR0ZqYXk1cGJ6Q0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQwpBUW9DZ2dFQkFLd01tTFhuQkNiRStZdTIvMlFtRGsxalRWQ3BvbHU3TlZmQlVFUWl1bDhFMHI2NFBLcDRZQ0c5Cmx2N2kwOHdFMEdJQUgydnJRQmxVd3p6ZW1SUWZ4YmQvYVNybzRHNUFBYTJsY2NMaFpqUlh2NEVMaER0aVg4N3IKaTQ0MWJ2Y01OM0ZPTlRuczJhRkJYcllLWGxpNG4rc0RzTEVuZmpWdXRiV01Zeis3M3ptaGZzclRJUjRzTXo3cQpmSzM2WFM4UkRjNW5oVVcyYU9BZ3lnbFZSOVVXRkxXNjNXYXVhcHg2QUpBR1RoZnJYdVVHZXlZUUVBSENxZmZmCjhyOEt3YTFYK1NwYm9YK1ppSVE0Nk5jQ043OFZnL2dQVHNLZmphZURoNWcyNlk1dEVidHd3MWdRbWlhK0MyRHIKWHpYNU13RzJGNHN0cG5kUnRQckZrU1VnMW1zd0xuc0NBd0VBQWFBQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQgpBUUFXR0JuRnhaZ0gzd0N3TG5IQ0xjb0l5RHJrMUVvYkRjN3BJK1VVWEJIS2JBWk9IWEFhaGJ5RFFLL2RuTHN3CjJkZ0J3bmlJR3kxNElwQlNxaDBJUE03eHk5WjI4VW9oR3piN0FVakRJWHlNdmkvYTJyTVhjWjI1d1NVQmxGc28Kd005dE1QU2JwcEVvRERsa3NsOUIwT1BPdkFyQ0NKNnZGaU1UbS9wMUJIUWJSOExNQW53U0lUYVVNSFByRzJVMgpjTjEvRGNMWjZ2enEyeENjYVoxemh2bzBpY1VIUm9UWmV1ZEp6MkxmR0VHM1VOb2ppbXpBNUZHd0RhS3BySWp3ClVkd1JmZWZ1T29MT1dNVnFNbGRBcTlyT24wNHJaT3Jnak1HSE9tTWxleVdPS1AySllhaDNrVDdKU01zTHhYcFYKV0ExQjRsLzFFQkhWeGlKQi9Zby9JQWVsCi0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo= + isCA: false + usages: + - signing + - digital signature + - server auth + # 90 days + duration: 2160h + issuerRef: + name: ca-issuer + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: Issuer + group: cert-manager.io +``` + +This `CertificateRequest` will make cert-manager attempt to request the `Issuer` +`ca-issuer` in the default issuer group `cert-manager.io`, return a +certificate based upon the certificate signing request. Other groups can be +specified inside the `issuerRef` which will change the targeted issuer to other +external, third party issuers you may have installed. + +The resource also exposes the option for stating the certificate as CA, Key +Usages, and requested validity duration. + +All fields within the `spec` of the `CertificateRequest`, as well as any managed +cert-manager annotations, are immutable and cannot be modified after creation. + +A successful issuance of the certificate signing request will cause an update to +the resource, setting the status with the signed certificate, the CA of the +certificate (if available), and setting the `Ready` condition to `True`. + +Whether issuance of the certificate signing request was successful or not, a retry of the +issuance will _not_ happen. It is the responsibility of some other controller to +manage the logic and life cycle of `CertificateRequests`. + +## Conditions +`CertificateRequests` have a set of strongly defined conditions that should be +used and relied upon by controllers or services to make decisions on what +actions to take next on the resource. + +### Ready +Each ready condition consists of the pair `Ready` - a boolean value, and +`Reason` - a string. The set of values and meanings are as follows: + +| Ready | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| False | Pending | The `CertificateRequest` is currently pending, waiting for some other operation to take place. This could be that the `Issuer` does not exist yet or the `Issuer` is in the process of issuing a certificate. | +| False | Failed | The certificate has failed to be issued - either the returned certificate failed to be decoded or an instance of the referenced issuer used for signing failed. No further action will be taken on the `CertificateRequest` by its controller and it can be considered terminally failed. | +| True | Issued | A signed certificate has been successfully issued by the referenced `Issuer`. | + +This condition should be set by the issuer. + +### Denied +| Denied | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| True | \ | The `CertificateRequest` was Denied by an approver. This `CertificateRequest` can be considered terminally failed. + +This condition should only be set by an approver. + +### Approved +| Approved | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| True | \ | The `CertificateRequest` was approved by the approver. This `CertificateRequest` is approved and can be issued by the issuer. + +This condition should only be set by an approver. + +### InvalidRequest +| InvalidRequest | Reason | Condition Meaning | +| ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| True | \ | The `CertificateRequest` is invalid. This `CertificateRequest` can be considered terminally failed. + + +## UserInfo + +`CertificateRequests` include a set of `UserInfo` fields as part of the spec, +namely: `username`, `groups`, `uid`, and `extra`. These values contain the user +who created the `CertificateRequest`. This user will be cert-manager itself in +the case that the `CertificateRequest` was created by a +[`Certificate`](./certificate.md) resource, or instead the user who created the +`CertificateRequest` directly. + +> **Warning**: These fields are managed by cert-manager and should _never_ be +> set or modified by anything else. When the `CertificateRequest` is created, +> these fields will be overridden, and any request attempting to modify them +> will be rejected. + + +### Approval +CertificateRequests can be `Approved` or `Denied`. These mutually exclusive +conditions gate a CertificateRequest from being signed by its managed signer. + +- A signer should _not_ sign a managed CertificateRequest without an Approved condition +- A signer _will_ sign a managed CertificateRequest with an Approved condition +- A signer will _never_ sign a managed CertificateRequest with a Denied condition + +These conditions are _permanent_, and cannot be modified or changed once set. + +```bash +NAMESPACE NAME APPROVED DENIED READY ISSUER REQUESTOR AGE +istio-system service-mesh-ca-whh5b True True mesh-ca system:serviceaccount:istio-system:istiod 16s +istio-system my-app-fj9sa True mesh-ca system:serviceaccount:my-app:my-app 4s +``` + + +#### Behavior + +The Approved and Denied conditions are two distinct condition types on the +CertificateRequest. These conditions must only have the status of True, and +are mutually exclusive (i.e. a CertificateRequest cannot have an Approved and +Denied condition simultaneously). This behavior is enforced in the cert-manager +validating admission webhook. + +An "approver" is an entity that is responsible for setting the Approved/Denied +conditions. It is up to the approver's implementation as to what +CertificateRequests are managed by that approver. + +The Reason field of the Approved/Denied condition should be set to *who* set the +condition. Who can be interpreted however makes sense to the approver +implementation. For example, it may include the API group of an approving policy +controller, or the client agent of a manual request. + +The Message field of the Approved/Denied condition should be set to *why* the +condition is set. Again, why can be interpreted however makes sense to the +implementation of the approver. For example, the name of the resource that +approves this request, the violations which caused the request to be denied, or +the team to who manually approved the request. + + +#### Approver Controller + +By default, cert-manager will run an internal approval controller which will +automatically approve _all_ CertificateRequests that reference any internal +issuer type in any namespace: `cert-manager.io/Issuer`, +`cert-manager.io/ClusterIssuer`. + +To disable this controller, add the following argument to the +cert-manager-controller: `--controllers=*,-certificaterequests-approver`. This +can be achieved with helm by appending: + +```bash +--set extraArgs={--controllers='*\,-certificaterequests-approver'} +``` + +Alternatively, in order for the internal approver controller to approve +CertificateRequests that reference an external issuer, add the following RBAC to +the cert-manager-controller Service Account. Please replace the given resource +names with the relevant names: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-approve:my-issuer-example-com # edit +rules: +- apiGroups: + - cert-manager.io + resources: + - signers + verbs: + - approve + resourceNames: + - issuers.my-issuer.example.com/* # edit + - clusterissuers.my-issuer.example.com/* # edit +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-approve:my-issuer-example-com # edit +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-approve:my-issuer-example-com # edit +subjects: +- kind: ServiceAccount + name: cert-manager + namespace: cert-manager +``` + +#### RBAC Syntax + +When a user or controller attempts to approve or deny a CertificateRequest, the +cert-manager webhook will evaluate whether it has sufficient permissions to do +so. These permissions are based upon the request +itself- specifically the request's IssuerRef: + +```yaml +apiGroups: ["cert-manager.io"] +resources: ["signers"] +verbs: ["approve"] +resourceNames: + # namesapced signers + - "./." + # cluster scoped signers + - "./" + # all signers of this resource name + - "./*" +``` + +An example ClusterRole that would grant the permissions to set the Approve and +Denied conditions of CertificateRequests that reference the cluster scoped +`myissuers` external issuer, in the group `my-example.io`, with the name `myapp`: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: my-example-io-my-issuer-myapp-approver +rules: + - apiGroups: ["cert-manager.io"] + resources: ["signers"] + verbs: ["approve"] + resourceNames: ["myissuers.my-example.io/myapp"] +``` + +If the approver does not have sufficient permissions defined above to set the +Approved or Denied conditions, the request will be rejected by the cert-manager +validating admission webhook. + +- The RBAC permissions *must* be granted at the cluster scope +- Namespaced signers are represented by a namespaced resource using the syntax of `./.` +- Cluster scoped signers are represented using the syntax of `./` +- An approver can be granted approval for all namespaces via `./*` +- The apiGroup must *always* be `cert-manager.io` +- The resource must *always* be `signers` +- The verb must *always* be `approve`, which grants the approver the permissions to set *both* Approved and Denied conditions + +An example of signing all `myissuer` signers in all namespaces, and +`clustermyissuers` with the name `myapp`, in the `my-example.io` group: + +```yaml + resourceNames: ["myissuers.my-example.io/*", "clustermyissuers.my-example.io/myapp"] +``` + +An example of signing `myissuer` with the name `myapp` in the namespaces `foo` +and `bar`: + +```yaml + resourceNames: ["myissuers.my-example.io/foo.myapp", "myissuers.my-example.io/bar.myapp"] +``` \ No newline at end of file diff --git a/content/v1.13-docs/concepts/issuer.md b/content/v1.13-docs/concepts/issuer.md new file mode 100644 index 0000000000..6293369992 --- /dev/null +++ b/content/v1.13-docs/concepts/issuer.md @@ -0,0 +1,44 @@ +--- +title: Issuer +description: 'cert-manager core concepts: Issuers and ClusterIssuers' +--- + +`Issuers`, and `ClusterIssuers`, are Kubernetes resources that represent +certificate authorities (CAs) that are able to generate signed certificates by honoring +certificate signing requests. All cert-manager certificates require a referenced +issuer that is in a ready condition to attempt to honor the request. + +An example of an `Issuer` type is `CA`. A simple `CA` `Issuer` is as follows: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ca-issuer + namespace: mesh-system +spec: + ca: + secretName: ca-key-pair +``` + +This is a simple `Issuer` that will sign certificates based on a private key. +The certificate stored in the secret `ca-key-pair` can then be used to trust +newly signed certificates by this `Issuer` in a Public Key Infrastructure (PKI) +system. + +## Namespaces + +An `Issuer` is a namespaced resource, and it is not possible to issue +certificates from an `Issuer` in a different namespace. This means you will need +to create an `Issuer` in each namespace you wish to obtain `Certificates` in. + +If you want to create a single `Issuer` that can be consumed in multiple +namespaces, you should consider creating a `ClusterIssuer` resource. This is +almost identical to the `Issuer` resource, however is non-namespaced so it +can be used to issue `Certificates` across all namespaces. + +## Supported Issuers + +cert-manager supports a number of 'in-tree', as well as 'out-of-tree' `Issuer` +types. An exhaustive list of these `Issuer` types can be found in the +cert-manager [configuration documentation](../configuration/README.md). diff --git a/content/v1.13-docs/concepts/webhook.md b/content/v1.13-docs/concepts/webhook.md new file mode 100644 index 0000000000..1bcbbee769 --- /dev/null +++ b/content/v1.13-docs/concepts/webhook.md @@ -0,0 +1,80 @@ +--- +title: All About the cert-manager Webhook +description: | + Learn about the webhook component of cert-manager, which validates, converts and sets default values for the cert-manager custom resources +--- + +cert-manager extends the Kubernetes API using Custom Resource Definitions. +It installs a webhook which has three main functions: + +- [Validation](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook): + Ensures that when cert-manager resources are created or updated, they conform + to the rules of the API. This validation is more in depth than for example + ensuring resources conform to the OpenAPI schema, but instead contains logic such as + not allowing to specify more than one `Issuer` type per `Issuer` resource. The + validating admission is always called and will respond with a success or + failed response. +- [Mutation / Defaulting](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook): + Changes the contents of resources during create and update operations, for + example to set default values. +- [Conversion](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion): + The webhook is also responsible for implementing a conversion over versions + in the cert-manager `CustomResources` (`cert-manager.io`). This means that + multiple API versions can be supported simultaneously; from `v1alpha2` through to `v1`. + This makes it possible to rely on a particular version of our + configuration schema. + +> ℹ️ This is known as Dynamic Admission Control. +> Read more about [Dynamic Admission Control](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) in the Kubernetes documentation. + +## Overview + +The webhook component is deployed as another pod that runs alongside the main +cert-manager controller and CA injector components. + +In order for the API server to communicate with the webhook component, the +webhook requires a TLS certificate that the apiserver is configured to trust. + +The [`cainjector`](./ca-injector.md) creates `secret/cert-manager-webhook-ca`, a self-signed root CA certificate which is used to sign certificates for the webhook pod. + +Then the webhook can be configured with either + +1. paths to a TLS certificate and key signed by the webhook CA, or +2. a reference to the CA Secret for dynamic generation of the certificate and key on webhook startup + +## Known Problems and Solutions + +### Webhook connection problems on GKE private cluster + +If errors occur around the webhook but the webhook is running then the webhook +is most likely not reachable from the API server. In this case, ensure that the +API server can communicate with the webhook by following the [GKE private +cluster explanation](../installation/compatibility.md#gke). + +### Webhook connection problems on AWS EKS + +When using a custom CNI (such as Weave or Calico) on EKS, the webhook cannot be reached by cert-manager. +This happens because the control plane cannot be configured to run on a custom CNI on EKS, +so the CNIs differ between control plane and worker nodes. +The solution is to [run the webhook in the host network](../installation/compatibility.md#aws-eks) so it can be reached by cert-manager. + +### Webhook connection problems shortly after cert-manager installation + +When you first install cert-manager, it will take a few seconds before the cert-manager API is usable. +This is because the cert-manager API requires the cert-manager webhook server, which takes some time to start up. +Here's why: + +* The webhook server performs a leader election at startup which may take a few seconds. +* The webhook server may take a few seconds to start up and to generate its self-signed CA and serving certificate and to publish those to a Secret. +* `cainjector` performs a leader election at start up which can take a few seconds. +* `cainjector`, once started, will take a few seconds to update the `caBundle` in all the webhook configurations. + +For these reasons, after installing cert-manager and when performing post-installation cert-manager API operations, +you will need to check for temporary API configuration errors and retry. + +You could also add a post-installation check which performs `kubectl --dry-run` operations on the cert-manager API. +Or you could add a post-installation check which automatically retries the [Installation Verification](../installation/verify.md) steps until they succeed. + +### Other Webhook Problems + +If you encounter any other problems with the webhook, please refer to the [webhook troubleshooting guide](../troubleshooting/webhook.md). diff --git a/content/v1.13-docs/configuration/README.md b/content/v1.13-docs/configuration/README.md new file mode 100644 index 0000000000..85c5d1334f --- /dev/null +++ b/content/v1.13-docs/configuration/README.md @@ -0,0 +1,30 @@ +--- +title: Issuer Configuration +description: Learn about configuring cert-manager using Issuer and ClusterIssuer resources. +--- + +The first thing you'll need to configure after you've installed cert-manager is an `Issuer` or a `ClusterIssuer`. +These are resources that represent certificate authorities (CAs) +able to sign certificates in response to certificate signing requests. + +This section documents how the different issuer types can be configured. You might want to +[read more about `Issuer` and `ClusterIssuer` resources](../concepts/issuer.md). + +cert-manager comes with a number of built-in certificate issuers which are denoted by being in +the `cert-manager.io` group. You can also install external issuers in addition to the built-in types. +Built-in and external issuers are treated the same and are configured similarly. + +## Cluster Resource Namespace + +When using `ClusterIssuer` resource types, ensure you understand the purpose of the +Cluster Resource Namespace; this can be a common source +of issues for people getting started with cert-manager. + +The `ClusterIssuer` resource is cluster scoped. This means that when referencing +a secret via the `secretName` field, secrets will be looked for in the `Cluster +Resource Namespace`. By default, this namespace is `cert-manager` however it can be +changed via a flag on the cert-manager-controller component: + +```bash +--cluster-resource-namespace=my-namespace +``` diff --git a/content/v1.13-docs/configuration/acme/README.md b/content/v1.13-docs/configuration/acme/README.md new file mode 100644 index 0000000000..47eb22c9ee --- /dev/null +++ b/content/v1.13-docs/configuration/acme/README.md @@ -0,0 +1,391 @@ +--- +title: ACME +description: 'cert-manager configuration: ACME Issuers' +--- + +The ACME Issuer type represents a single account registered with the Automated +Certificate Management Environment (ACME) Certificate Authority server. When you +create a new ACME `Issuer`, cert-manager will generate a private key which is +used to identify you with the ACME server. + +Certificates issued by public ACME servers are typically trusted by client's +computers by default. This means that, for example, visiting a website that is +backed by an ACME certificate issued for that URL, will be trusted by default by +most client's web browsers. ACME certificates are typically free. + +## Solving Challenges + +In order for the ACME CA server to verify that a client owns the domain, or +domains, a certificate is being requested for, the client must complete +"challenges". This is to ensure clients are unable to request certificates for +domains they do not own and as a result, fraudulently impersonate another's +site. As detailed in the [RFC8555](https://tools.ietf.org/html/rfc8555), +cert-manager offers two challenge validations - HTTP01 and DNS01 challenges. + +[HTTP01](./http01/README.md) challenges are completed by presenting a computed +key, that should be present at a HTTP URL endpoint and is routable over the +internet. This URL will use the domain name requested for the certificate. Once +the ACME server is able to get this key from this URL over the internet, the +ACME server can validate you are the owner of this domain. When a HTTP01 +challenge is created, cert-manager will automatically configure your cluster +ingress to route traffic for this URL to a small web server that presents this +key. + +[DNS01](./dns01/README.md) challenges are completed by providing a computed key +that is present at a DNS TXT record. Once this TXT record has been propagated +across the internet, the ACME server can successfully retrieve this key via a +DNS lookup and can validate that the client owns the domain for the requested +certificate. With the correct permissions, cert-manager will automatically +present this TXT record for your given DNS provider. + +## Configuration + +### Creating a Basic ACME Issuer + +All ACME `Issuers` follow a similar configuration structure - a clients `email`, +a `server` URL, a `privateKeySecretRef`, and one or more `solvers`. Below is an +example of a simple ACME issuer: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + # You must replace this email address with your own. + # Let's Encrypt will use this to contact you about expiring + # certificates, and issues related to your account. + email: user@example.com + server: https://acme-staging-v02.api.letsencrypt.org/directory + privateKeySecretRef: + # Secret resource that will be used to store the account's private key. + name: example-issuer-account-key + # Add a single challenge solver, HTTP01 using nginx + solvers: + - http01: + ingress: + ingressClassName: nginx +``` + +Solvers come in the form of [`dns01`](./dns01/README.md) and +[`http01`](./http01/README.md) stanzas. For more information on how to configure +these solver types, visit their respective documentation - +[DNS01](./dns01/README.md), [HTTP01](./http01/README.md). + +### External Account Bindings + +cert-manager supports using External Account Bindings with your ACME account. +External Account Bindings are used to associate your ACME account with an +external account such as a CA custom database. This is typically not needed for +most cert-manager users unless you know it is explicitly needed. + +External Account Bindings require two fields on an ACME `Issuer` which +represents your ACME account. These fields are: + +- `keyID` - the key ID or account ID of which your external account binding is indexed by the +external account manager +- `keySecretRef` - the name and key of a secret containing a base 64 encoded +URL string of your external account symmetric MAC key + +> Note: In _most_ cases, the MAC key must be encoded in `base64URL`. The +> following command will base64-encode a key and convert it to `base64URL`: +> +> ```console +> $ echo 'my-secret-key' | base64 -w0 | sed -e 's/+/-/g' -e 's/\//_/g' -e 's/=//g' +> ``` +> +> You can then create the Secret resource with: +> +> ```console +> $ kubectl create secret generic eab-secret --from-literal \ +> secret={base64 encoded secret key} +> ``` + +An example of an ACME issuer with an External Account Binding is as follows. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: my-acme-server-with-eab +spec: + acme: + email: user@example.com + server: https://my-acme-server-with-eab.com/directory + externalAccountBinding: + keyID: my-keyID-1 + keySecretRef: + name: eab-secret + key: secret + privateKeySecretRef: + name: example-issuer-account-key + solvers: + - http01: + ingress: + ingressClassName: nginx +``` + +> Note: cert-manager versions pre-`v1.3.0` also required users to specify the +> MAC algorithm for EAB by setting +> `Issuer.spec.acme.externalAccountBinding.keyAlgorithm` field. This field is +> now deprecated because the upstream Go `x/crypto` library hardcodes the algorithm +> to `HS256`. (See related discussion upstream +> [`CL#41430`](https://github.com/golang/go/issues/41430)). +### Reusing an ACME Account + +You may want to reuse a single ACME account across multiple clusters. This +might especially be useful when using EAB. If the `disableAccountKeyGeneration` +field is set, cert-manager will not create a new ACME account and use the +existing key specified in `privateKeySecretRef`. Note that the +`Issuer`/`ClusterIssuer` will not be ready and will continue to retry until the +`Secret` is provided. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: my-acme-server-with-existing-acme-account +spec: + acme: + email: user@example.com + disableAccountKeyGeneration: true + privateKeySecretRef: + name: example-issuer-account-key +``` + + +### Adding Multiple Solver Types + +You may want to use different types of challenge solver configurations for +different ingress controllers, for example if you want to issue wildcard +certificates using `DNS01` alongside other certificates that are validated using +`HTTP01`. + +The `solvers` stanza has an optional `selector` field, that can be used to +specify which `Certificates`, and further, what DNS names *on those* +`Certificates` should be used to solve challenges. + +There are three selector types that can be used to form the requirements that a +`Certificate` must meet in order to be selected for a solver - `matchLabels`, +`dnsNames` and `dnsZones`. You can have any number of these three selectors on a +single solver. + + +#### Match Labels + +The `matchLabel` selector requires that all `Certificates` match all of +the labels that are defined in the string map list of that stanza. For example, +the following `Issuer` will only match on `Certificates` that have the labels +`"user-cloudflare-solver": "true"` and `"email": "user@example.com"`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + matchLabels: + "use-cloudflare-solver": "true" + "email": "user@example.com" +``` + +#### DNS Names + +The `dnsNames` selector is a list of exact DNS names that should be mapped to a +solver. This means that `Certificates` containing any of these DNS names will +be selected. If a match is found, a `dnsNames` selector will take precedence +over a [`dnsZones`](#dns-zones) selector. If multiple solvers match with the +same `dnsNames` value, the solver with the most matching labels in +[`matchLabels`](#match-labels) will be selected. If neither has more matches, +the solver defined earlier in the list will be selected. + +The following example will solve challenges of `Certificates` with DNS names +`example.com` and `*.example.com` for these domains. + +> Note: `dnsNames` take an exact match and do not resolve wildcards, meaning the +> following `Issuer` *will not* solve for DNS names such as `foo.example.com`. +> Use the [`dnsZones`](#dns-zones) selector type to match all subdomains within +> a zone. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + dnsNames: + - 'example.com' + - '*.example.com' +``` + +#### DNS Zones + +The `dnsZones` stanza defines a list of DNS zones that can be solved by this +solver. If a DNS name is an exact match, or a subdomain of any of the specified +`dnsZones`, this solver will be used, unless a more specific +[`dnsNames`](#dns-names) match is configured. This means that `sys.example.com` +will be selected over one specifying `example.com` for the domain +`www.sys.example.com`. If multiple solvers match with the same `dnsZones` value, +the solver with the most matching labels in [`matchLabels`](#match-labels) will +be selected. If neither has more matches, the solver defined earlier in the list +will be selected. + +In the following example, this solver will resolve challenges for the domain +`example.com`, as well as all of its subdomains `*.example.com`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + dnsZones: + - 'example.com' +``` + +#### All Together + +Each solver is able to have any number of the three selector types defined. In +the following example, the `DNS01` solver for CloudFlare will be used to solve +challenges for domains for `Certificates` that contain the DNS names +`a.example.com` and `b.example.com`. The `DNS01` solver for Google CloudDNS will +be used to solve challenges for `Certificates` whose DNS names match +zone `test.example.com` and all of its subdomains (e.g. `foo.test.example.com`). + +For all other challenges, the `HTTP01` solver will be used *only* if the +`Certificate` also contains the label `"use-http01-solver": "true"`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + ... + solvers: + - http01: + ingress: + ingressClassName: nginx + selector: + matchLabels: + "use-http01-solver": "true" + - dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + dnsNames: + - 'a.example.com' + - 'b.example.com' + - dns01: + cloudDNS: + project: my-project-id + hostedZoneName: 'test-example.com' + serviceAccountSecretRef: + key: sa + name: gcp-sa-secret + selector: + dnsZones: + - 'test.example.com' # This should be the DNS name of the zone +``` + +Each individual selector block can contain more than one selector type for +example: + +```yaml +solvers: +- dns01: + cloudflare: + email: user@example.com + apiKeySecretRef: + name: cloudflare-apikey-secret + key: apikey + selector: + matchLabels: + 'email': 'user@example.com' + 'solver': 'cloudflare' + dnsZones: + - 'test.example.com' + - 'example.dev' +``` + +In this case the `DNS01` solver for Cloudflare will only be used to solve a +challenge for a DNS name if the `Certificate` has a label from +`matchLabels` _and_ the DNS name matches a zone from `dnsZones`. + +## Private ACME Servers + +cert-manager should also work with private or self-hosted ACME servers, as long as they follow the ACME spec. + +If your ACME server doesn't use a publicly trusted certificate, you can pass a trusted CA to use when creating your +issuer, from cert-manager 1.11 onwards: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: my-acme-server-issuer +spec: + acme: + server: https://my-acme-server.example.com + caBundle: + ... +``` + + +{/* The empty link below preserves old links to #alternative-certificate-chain", which matched the old title of this section */} + +## Alternative Certificate Chains + +It's possible to choose alternative certificate chains when fetching a certificate from an ACME server. This allows issuers to gracefully roll people over to a new root certificate during a transition period; the most famous example was the Let's Encrypt ["ISRG Root" changeover](https://community.letsencrypt.org/t/transition-to-isrgs-root-delayed-until-jan-11-2021/125516). + +This functionality is not exclusive to Let's Encrypt; if your ACME server supports signing by multiple CAs you can use `preferredChain` with the value of the Common Name of the chain you want in the Issuer part of the certificate. If the common name matches a difference chain, the server can choose to use and return that new chain. + +If the `preferredChain` does not match a certificate the server will return whatever it considers to be its default certificate. + +By way of an example, below is how a user would have requested an alternative chain before the (now completed) "ISRG Root" changeover, but note that since this change has already happened there's no need for this with Let's Encrypt any more: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + preferredChain: "ISRG Root X1" +``` diff --git a/content/v1.13-docs/configuration/acme/dns01/README.md b/content/v1.13-docs/configuration/acme/dns01/README.md new file mode 100644 index 0000000000..d952eef568 --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/README.md @@ -0,0 +1,188 @@ +--- +title: DNS01 +description: 'cert-manager configuration: ACME DNS-01 challenges overview' +--- + +## Configuring DNS01 Challenge Provider + +This page contains details on the different options available on the `Issuer` +resource's DNS01 challenge solver configuration. + +For more information on configuring ACME `Issuers` and their API format, read the +[ACME Issuers](../README.md) documentation. + +DNS01 provider configuration must be specified on the `Issuer` resource, similar +to the examples in the setting up documentation. + +You can read about how the DNS01 challenge type works on the [Let's Encrypt +challenge types +page](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge). + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + email: user@example.com + server: https://acme-staging-v02.api.letsencrypt.org/directory + privateKeySecretRef: + name: example-issuer-account-key + solvers: + - dns01: + cloudDNS: + project: my-project + serviceAccountSecretRef: + name: prod-clouddns-svc-acct-secret + key: service-account.json +``` + +Each issuer can specify multiple different DNS01 challenge providers, and +it is also possible to have multiple instances of the same DNS provider on a +single `Issuer` (e.g. two CloudDNS accounts could be set, each with their own +name). + +For more information on utilizing multiple solver types on a single `Issuer`, +read the multiple-solver-types section. + +## Setting Nameservers for DNS01 Self Check + +cert-manager will check the correct DNS records exist before attempting a DNS01 +challenge. By default cert-manager will use the recursive nameservers taken +from `/etc/resolv.conf` to query for the authoritative nameservers, which it will +then query directly to verify the DNS records exist. + +If this is not desired (for example with multiple authoritative nameservers or +split-horizon DNS), the cert-manager controller exposes two flags that allows +you alter this behavior: + +`--dns01-recursive-nameservers` Comma separated string with host and port of the +recursive nameservers cert-manager should query. + +`--dns01-recursive-nameservers-only` Forces cert-manager to only use the +recursive nameservers for verification. Enabling this option could cause the DNS01 +self check to take longer due to caching performed by the recursive nameservers. + + +Example usage: +```bash +--dns01-recursive-nameservers-only --dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53 +``` + +If you're using the `cert-manager` helm chart, you can set recursive nameservers +through `.Values.extraArgs` or at the command at helm install/upgrade time +with `--set`: + +```bash +--set 'extraArgs={--dns01-recursive-nameservers-only,--dns01-recursive-nameservers=8.8.8.8:53\,1.1.1.1:53}' +``` + +## Delegated Domains for DNS01 + +By default, cert-manager will not follow CNAME records pointing to subdomains. + +If granting cert-manager access to the root DNS zone is not desired, then the +`_acme-challenge.example.com` subdomain can instead be delegated to some other, +less privileged domain (`less-privileged.example.org`). This could be achieved in the following way. Say, one has two zones: + +* `example.com` +* `less-privileged.example.org` + +1. Create a CNAME record pointing to this less privileged domain: +``` +_acme-challenge.example.com IN CNAME _acme-challenge.less-privileged.example.org. +``` + +2. Grant cert-manager rights to update less privileged `less-privileged.example.org` zone + +3. Provide configuration/credentials for updating this less privileged zone +and add an additional field into the relevant `dns01` solver. Note that `selector` +field is still working for the original `example.com`, while credentials are provided for +`less-privileged.example.org` + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + ... +spec: + acme: + ... + solvers: + - selector: + dnsZones: + - 'example.com' + dns01: + # Valid values are None and Follow + cnameStrategy: Follow + route53: + region: eu-central-1 + accessKeyID: + hostedZoneID: + secretAccessKeySecretRef: + ... +``` + +If you have a multitude of (sub)domains requiring separate certificates, +it is possible to share an aliased less-privileged domain. To achieve it one should +create a CNAME record for each (sub)domain like this: + +```txt +_acme-challenge.example.com IN CNAME _acme-challenge.less-privileged.example.org. +_acme-challenge.www.example.com IN CNAME _acme-challenge.less-privileged.example.org. +_acme-challenge.foo.example.com IN CNAME _acme-challenge.less-privileged.example.org. +_acme-challenge.bar.example.com IN CNAME _acme-challenge.less-privileged.example.org. +``` + +With this configuration cert-manager will follow CNAME records recursively in order to determine +which DNS zone to update during DNS01 challenges. + + +## Supported DNS01 providers + +A number of different DNS providers are supported for the ACME `Issuer`. Below +is a listing of available providers, their `.yaml` configurations, along with +additional Kubernetes and provider specific notes regarding their usage. + +- [ACMEDNS](./acme-dns.md) +- [Akamai](./akamai.md) +- [AzureDNS](./azuredns.md) +- [CloudFlare](./cloudflare.md) +- [Google](./google.md) +- [Route53](./route53.md) +- [DigitalOcean](./digitalocean.md) +- [RFC2136](./rfc2136.md) + +## Webhook + +cert-manager also supports out of tree DNS providers using an external webhook. +Links to these supported providers along with their documentation are below: + +- [`AliDNS-Webhook`](https://github.com/pragkent/alidns-webhook) +- [`cert-manager-alidns-webhook`](https://github.com/DEVmachine-fr/cert-manager-alidns-webhook) +- [`cert-manager-webhook-civo`](https://github.com/okteto/cert-manager-webhook-civo) +- [`cert-manager-webhook-dnspod`](https://github.com/qqshfox/cert-manager-webhook-dnspod) +- [`cert-manager-webhook-dnsimple`](https://github.com/neoskop/cert-manager-webhook-dnsimple) +- [`cert-manager-webhook-gandi`](https://github.com/bwolf/cert-manager-webhook-gandi) +- [`cert-manager-webhook-infomaniak`](https://github.com/Infomaniak/cert-manager-webhook-infomaniak) +- [`cert-manager-webhook-inwx`](https://gitlab.com/smueller18/cert-manager-webhook-inwx) +- [`cert-manager-webhook-linode`](https://github.com/slicen/cert-manager-webhook-linode) +- [`cert-manager-webhook-oci`](https://gitlab.com/dn13/cert-manager-webhook-oci) (Oracle Cloud Infrastructure) +- [`cert-manager-webhook-scaleway`](https://github.com/scaleway/cert-manager-webhook-scaleway) +- [`cert-manager-webhook-selectel`](https://github.com/selectel/cert-manager-webhook-selectel) +- [`cert-manager-webhook-softlayer`](https://github.com/cgroschupp/cert-manager-webhook-softlayer) +- [`cert-manager-webhook-ibmcis`](https://github.com/jb-dk/cert-manager-webhook-ibmcis) +- [`cert-manager-webhook-loopia`](https://github.com/Identitry/cert-manager-webhook-loopia) +- [`cert-manager-webhook-arvan`](https://github.com/kiandigital/cert-manager-webhook-arvan) +- [`bizflycloud-certmanager-dns-webhook`](https://github.com/bizflycloud/bizflycloud-certmanager-dns-webhook) +- [`cert-manager-webhook-hetzner`](https://github.com/vadimkim/cert-manager-webhook-hetzner) +- [`cert-manager-webhook-yandex-cloud`](https://github.com/malinink/cert-manager-webhook-yandex-cloud) +- [`cert-manager-webhook-netcup`](https://github.com/aellwein/cert-manager-webhook-netcup) +- [`cert-manager-webhook-pdns`](https://github.com/zachomedia/cert-manager-webhook-pdns) +- [`cert-manager-webhook-zilore`](https://gitlab.com/zilore/cert-manager-webhook-zilore) +- [`stackit-cert-manager-webhook`](https://github.com/stackitcloud/stackit-cert-manager-webhook) + +You can find more information on how to configure webhook providers [here](./webhook.md). + +To create a new unsupported DNS provider, follow the development documentation [here](../../../contributing/dns-providers.md). diff --git a/content/v1.13-docs/configuration/acme/dns01/acme-dns.md b/content/v1.13-docs/configuration/acme/dns01/acme-dns.md new file mode 100644 index 0000000000..968531bf5f --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/acme-dns.md @@ -0,0 +1,220 @@ +--- +title: ACMEDNS +description: 'cert-manager configuration: ACME DNS-01 challenges using ACMEDNS' +--- + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + solvers: + - dns01: + acmeDNS: + host: https://acme.example.com + accountSecretRef: + name: acme-dns + key: acmedns.json +``` + +In general, clients to ACMEDNS perform registration on the users behalf and +inform them of the CNAME entries they must create. This is not possible in +cert-manager, it is a non-interactive system. Registration must be carried out +beforehand and the resulting credentials JSON uploaded to the cluster as a +`Secret`. In this example, we use `curl` and the API endpoints directly. +Information about setting up and configuring ACMEDNS is available on the +[ACMEDNS project page](https://github.com/joohoi/acme-dns). + +1. First, register with the ACMEDNS server, in this example, there is one + running at `auth.example.com`. The command: + + ```sh + curl -X POST http://auth.example.com/register + ``` + + will return a JSON with credentials for your registration: + + ```json + { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": [] + } + ``` + + It is strongly recommended to restrict the update endpoint to the IP + range of your pods. This is done at registration time as follows: + + ```sh + curl -X POST http://auth.example.com/register \ + -H "Content-Type: application/json" \ + --data '{"allowfrom": ["10.244.0.0/16"]}' + ``` + + Make sure to update the `allowfrom` field to match your cluster + configuration. The JSON will now look like: + + ```json + { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + } + ``` + +2. Save this JSON to a file with the key as your domain. You can specify + multiple domains with the same credentials if you like. In our example, + the returned credentials can be used to verify ownership of + `example.com` and and `example.org`. + + ```json + { + "example.com": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + }, + "example.org": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + } + } + ``` + +3. Next, update your primary DNS server with the CNAME record that will tell the + verifier how to locate the challenge TXT record. This is obtained from the + `fulldomain` field in the registration: + + ``` + _acme-challenge.example.com CNAME d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com + _acme-challenge.example.org CNAME d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com + ``` + + The "name" of the record always has the _acme-challenge subdomain, and + the "value" of the record matches exactly the fulldomain field from + registration. + + At verification time, the domain name `d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com` will be a TXT + record that is set to your validation token. When the verifier queries `_acme-challenge.example.com`, it will + be directed to the correct location by this CNAME record. This proves that you control `example.com` + +4. Create a secret from the credentials JSON that was saved in step 2, this + secret is referenced in the `accountSecretRef` field of your DNS01 + issuer settings. When creating an `Issuer` both this `Issuer` and + `Secret` must be in the same namespace. However for a `ClusterIssuer` + (which does not have a namespace) the `Secret` must be placed in the + same namespace as where the cert-manager pod is running in (in the + default setup `cert-manager`). + + ```sh + kubectl create secret generic acme-dns --from-file acmedns.json + ``` + +## Limitation of the `acme-dns` server + +The [`acme-dns`](https://github.com/joohoi/acme-dns) server has a [known +limitation](https://github.com/cert-manager/cert-manager/issues/3610#issuecomment-849792721): +when a set of credentials is used with more than 2 domains, cert-manager +will fail solving the DNS01 challenges. + +Imagining that you have configured the ACMEDNS issuer with a single set of +credentials, and that the "subdomain" of this set of credentials is +`d420c923-bbd7-4056-ab64-c3ca54c9b3cf`: + +```yaml +kind: Secret +metadata: + name: auth-example-com +stringData: + acmedns.json: | + { + "example.com": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + }, + } +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: my-acme-dns +spec: + acme: + solvers: + - dns01: + acmeDNS: + accountSecretRef: + name: auth-example-com + key: acmedns.json + host: auth.example.com +``` + +and imagine that you want to create a Certificate with three subdomains: + +```yaml +kind: Certificate +spec: + issuerRef: + name: issuer-1 + dnsNames: + - "example.com" + - "*.example.com" + - "foo.example.com" +``` + +cert-manager will only be able to solve 2 challenges out of 3 in a non +deterministic way. This limitation comes from a "feature" mentioned [this +acme-dns issue](https://github.com/joohoi/acme-dns/issues/76). + +One workaround is to issue one set of acme-dns credentials for each +domain that we want to be challenged, keeping in mind that each acme-dns +"subdomain" can only accept at most 2 challenged domains. For example, the +above secret would become: + +```yaml +kind: Secret +metadata: + name: auth-example-com +stringData: + acmedns.json: | + { + "example.com": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + }, + "foo.example.com": { + "username": "eabcdb41-d89f-4580-826f-3e62e9755ef2", + "password": "pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0", + "fulldomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com", + "subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", + "allowfrom": ["10.244.0.0/16"] + } +``` + +With this setup, we have: + +- `example.com` and `*.example.com` are registered in the acme-dns + "subdomain" `d420c923-bbd7-4056-ab64-c3ca54c9b3cf`. +- `foo.example.com` is registered in the acme-dns "subdomain" + `d420c923-bbd7-4056-ab64-c3ca54c9b3cf`. + +Another workaround is to use `--max-concurrent-challenges 2` when running +the `cert-manager-controller`. With this setting, acme-dns will only have 2 +TXT records in its database at any time, which mitigates the issue. \ No newline at end of file diff --git a/content/v1.13-docs/configuration/acme/dns01/akamai.md b/content/v1.13-docs/configuration/acme/dns01/akamai.md new file mode 100644 index 0000000000..271f7fd620 --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/akamai.md @@ -0,0 +1,86 @@ +--- +title: Akamai +description: 'cert-manager configuration: ACME DNS-01 challenges using Akamai DNS' +--- + +## Edge DNS + +Use Edge DNS to solve DNS01 ACME challenges by creating a `Secret` using [Akamai API credentials](https://developer.akamai.com/getting-started/edgegrid) and an `Issuer` that references the `Secret` and sets the solver type. + +### Create a Secret + +The `Secret` should look like the following for the `Issuer` to reference. Replace `use_akamai_client_secret`, `use_akamai_access_token` and `use_akamai_client_token` with the respective Akamai API credential values. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: akamai-secret +type: Opaque +stringData: + clientSecret: use_akamai_client_secret + accessToken: use_akamai_access_token + clientToken: use_akamai_client_token +``` + +### Create an Issuer + +To set Edge DNS for challenge tokens, `cert-manager` uses an `Issuer` that references the above `Secret` and other attributes such as the solver type. The `Issuer` should look like the following. Replace `use_akamai_host` with the Akamai API credential `host` value. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-akamai-dns +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: contact@me.com + privateKeySecretRef: + name: letsencrypt-akamai-issuer-account-key + solvers: + - dns01: + akamai: + serviceConsumerDomain: use_akamai_host + clientTokenSecretRef: + name: akamai-secret + key: clientToken + clientSecretSecretRef: + name: akamai-secret + key: clientSecret + accessTokenSecretRef: + name: akamai-secret + key: accessToken +``` + +### Create a Certificate + +The `Certificate` should look like the following and reference the Akamai Edge DNS `Issuer` above. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-zone +spec: + secretName: akamai-crt-secret + dnsNames: + - '*.example.zone' + issuerRef: + name: letsencrypt-akamai-dns + kind: Issuer +``` + +> Note: `cert-manager` will wait for challenge tokens to propagate across the Edge DNS network. Follow the `certificate` status with a command such as the following. + +```bash +kubectl describe certificate example-zone +``` + +### Troubleshooting + +Follow the `cert-manager` events to identify any issues with a command such as the following. + +```bash +cmctl status certificate example-zone +``` \ No newline at end of file diff --git a/content/v1.13-docs/configuration/acme/dns01/azuredns.md b/content/v1.13-docs/configuration/acme/dns01/azuredns.md new file mode 100644 index 0000000000..bc5de32fec --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/azuredns.md @@ -0,0 +1,505 @@ +--- +title: AzureDNS +description: 'cert-manager configuration: ACME DNS-01 challenges using AzureDNS' +--- + +cert-manager can create and then delete DNS-01 records in Azure DNS but it needs to authenticate to Azure first. +There are four authentication methods available: + +- [Managed Identity Using AAD Workload Identity](#managed-identity-using-aad-pod-identity) (recommended) +- [Managed Identity Using AAD Pod Identities](#managed-identity-using-aad-pod-identities) (deprecated) +- [Managed Identity Using AKS Kubelet Identity](#managed-identity-using-aks-kubelet-identity) +- [Service Principal](#service-principal) + +## Managed Identity Using AAD Workload Identity + +> ℹ️ This feature is available in cert-manager `>= v1.11.0`. +> +> 📖 Read the [AKS + LoadBalancer + Let's Encrypt tutorial](../../../tutorials/getting-started-aks-letsencrypt/README.md) for an end-to-end example of this authentication method. + +Azure AD workload identity (preview) on Azure Kubernetes Service (AKS) allows cert-manager to authenticate to Azure using a Kubernetes ServiceAccount Token and then to manage DNS-01 records in Azure DNS. +This is the recommended authentication method because it is more secure and easier to maintain than the other methods. + +### Reconfigure the cluster + +Enable the workload identity federation features on your cluster. +If you have an Azure AKS cluster you can use the following command: + +```bash +az aks update \ + --name ${CLUSTER} \ + --enable-oidc-issuer \ + --enable-workload-identity # ℹ️ This option is currently only available when using the aks-preview extension. +``` + +> ℹ️ You can [install the Azure workload identity extension on other managed and self-managed clusters](https://azure.github.io/azure-workload-identity/docs/installation.html) if you are not using Azure AKS. +> +> 📖 Read [Deploy and configure workload identity on an Azure Kubernetes Service (AKS) cluster](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster) for more information about the `--enable-workload-identity` feature. +> +### Reconfigure cert-manager + +Label the cert-manager controller Pod and ServiceAccount for the attention of the Azure Workload Identity webhook, +which will result in the cert-manager controller Pod having an extra volume containing a Kubernetes ServiceAccount token which it will use to authenticate with Azure. + +If you installed cert-manager using Helm, the labels can be configured using Helm values: + +```yaml +# values.yaml +podLabels: + azure.workload.identity/use: "true" +serviceAccount: + labels: + azure.workload.identity/use: "true" +``` + +If successful, the cert-manager Pod will have some new environment variables set, +and the Azure workload-identity ServiceAccount token as a projected volume: + +```bash +kubectl describe pod -n cert-manager -l app.kubernetes.io/component=controller +``` + +```terminal +Containers: + ... + cert-manager-controller: + ... + Environment: + ... + AZURE_CLIENT_ID: + AZURE_TENANT_ID: f99bd6a4-665c-41cf-aff1-87a89d5c62d4 + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + AZURE_AUTHORITY_HOST: https://login.microsoftonline.com/ + Mounts: + /var/run/secrets/azure/tokens from azure-identity-token (ro) +Volumes: + ... + azure-identity-token: + Type: Projected (a volume that contains injected data from multiple sources) + TokenExpirationSeconds: 3600 +``` + +> 📖 Read about [the role of the Mutating Admission Webhook](https://azure.github.io/azure-workload-identity/docs/installation/mutating-admission-webhook.html) in Azure AD Workload Identity for Kubernetes. + + +### Create a Managed Identity + +In order for cert-manager to use the Azure API and manipulate the records in the Azure DNS zone, +it needs an Azure account and the best type of account to use is called a "Managed Identity". +This account does not come with a password or an API key and it is designed for use by machines rather than humans. + +Choose a managed identity name and create the Managed Identity: + +```bash +export IDENTITY_NAME=cert-manager +az identity create --name "${IDENTITY_NAME}" +``` + +Grant it permission to modify the DNS zone records: + +```bash +export IDENTITY_CLIENT_ID=$(az identity show --name "${IDENTITY_NAME}" --query 'clientId' -o tsv) +az role assignment create \ + --role "DNS Zone Contributor" \ + --assignee IDENTITY_CLIENT_ID \ + --scope $(az network dns zone show --name $DOMAIN_NAME -o tsv --query id) +``` + +> 📖 Read [What are managed identities for Azure resources?](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) +> for an overview of managed identities and their uses. +> +> 📖 Read [Azure built-in roles](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) to learn about the "DNS Zone Contributor" role. +> +> 📖 Read more about [the `az identity` command](https://learn.microsoft.com/en-us/cli/azure/identity). + +### Add a Federated Identity + +Now associate a federated identity with the managed identity that you created earlier. +cert-manager will authenticate to Azure using a short lived Kubernetes ServiceAccount token, +and it will be able to impersonate the managed identity that you created in the previous step. + +```bash +export SERVICE_ACCOUNT_NAME=cert-manager # ℹ️ This is the default Kubernetes ServiceAccount used by the cert-manager controller. +export SERVICE_ACCOUNT_NAMESPACE=cert-manager # ℹ️ This is the default namespace for cert-manager. +export SERVICE_ACCOUNT_ISSUER=$(az aks show --resource-group $AZURE_DEFAULTS_GROUP --name $CLUSTER --query "oidcIssuerProfile.issuerUrl" -o tsv) +az identity federated-credential create \ + --name "cert-manager" \ + --identity-name "${IDENTITY_NAME}" \ + --issuer "${SERVICE_ACCOUNT_ISSUER}" \ + --subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}" +``` + +- `--subject`: is the distinguishing name of the Kubernetes ServiceAccount. +- `--issuer`: is a URL from which the Azure will download the JWT signing certificate and other metadata + +> 📖 Read about [Workload identity federation](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation) in the Microsoft identity platform documentation. +> +> 📖 Read more about [the `az identity federated-credential` command](https://learn.microsoft.com/en-us/cli/azure/identity/federated-credential). + +### Configure a ClusterIssuer + +For example: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: $EMAIL_ADDRESS + privateKeySecretRef: + name: letsencrypt-staging + solvers: + - dns01: + azureDNS: + hostedZoneName: $AZURE_ZONE_NAME + resourceGroupName: $AZURE_RESOURCE_GROUP + subscriptionID: $AZURE_SUBSCRIPTION_ID + environment: AzurePublicCloud + managedIdentity: + clientID: $IDENTITY_CLIENT_ID +``` + +The following variables need to be filled in. + +```bash +# An email address to which Let's Encrypt will send renewal reminders. +export EMAIL_ADDRESS= +# The Azure DNS zone in which the DNS-01 records will be created and deleted. +export AZURE_ZONE_NAME= +# The Azure resource group containing the DNS zone. +export AZURE_RESOURCE_GROUP= +# The Azure billing account name and ID for the DNS zone. +export AZURE_SUBSCRIPTION= +export AZURE_SUBSCRIPTION_ID=$(az account show --name $AZURE_SUBSCRIPTION --query 'id' -o tsv) +``` + +#### ⚠️ Using 'Ambient Credentials' with ClusterIssuer and Issuer resources + +This authentication method is an example of what cert-manager calls 'ambient credentials'. +Ambient credentials are enabled by default for ClusterIssuer resources, but disabled by default for Issuer resources. +This is to prevent unprivileged users, who have permission to create Issuer resources, from issuing certificates using credentials that cert-manager incidentally has access to. +ClusterIssuer resources are cluster scoped (not namespaced) and only platform administrators should be granted permission to create them. + +If you are using this authentication mechanism and ambient credentials are not enabled, you will see this error: + +```bash +error instantiating azuredns challenge solver: ClientID is not set but neither --cluster-issuer-ambient-credentials nor --issuer-ambient-credentials are set. +``` + +> ⚠️ It is possible (but not recommended) to enable this authentication mechanism for `Issuer` resources, by setting the `--issuer-ambient-credentials` flag on the cert-manager controller to true. + +## Managed Identity Using AAD Pod Identities + +> ⚠️ The [open source Azure AD pod-managed identity (preview) in Azure Kubernetes Service has been deprecated as of 10/24/2022](https://github.com/Azure/aad-pod-identity#-announcement). +> Use Workload Identity instead. + +[AAD Pod Identities](https://azure.github.io/aad-pod-identity) allows assigning a [Managed Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) to a pod. This removes the need for adding explicit credentials into the cluster to create the required DNS records. + +> Note: When using Pod identity, even though assigning multiple identities to a single pod is allowed, currently cert-manager does not support this as it is not able to identify which identity to use. + +Firstly an identity should be created that has access to contribute to the DNS Zone. + +- Example creation using `azure-cli` and `jq`: + +```bash +# Choose a unique Identity name and existing resource group to create identity in. +IDENTITY=$(az identity create --name $IDENTITY_NAME --resource-group $IDENTITY_GROUP --output json) + +# Gets principalId to use for role assignment +PRINCIPAL_ID=$(echo $IDENTITY | jq -r '.principalId') + +# Used for identity binding +CLIENT_ID=$(echo $IDENTITY | jq -r '.clientId') +RESOURCE_ID=$(echo $IDENTITY | jq -r '.id') + +# Get existing DNS Zone Id +ZONE_ID=$(az network dns zone show --name $ZONE_NAME --resource-group $ZONE_GROUP --query "id" -o tsv) + +# Create role assignment +az role assignment create --role "DNS Zone Contributor" --assignee $PRINCIPAL_ID --scope $ZONE_ID +``` + +- Example creation using Terraform + +```terraform +variable resource_group_name {} +variable location {} +variable dns_zone_id {} + +# Creates Identity +resource "azurerm_user_assigned_identity" "dns_identity" { + name = "cert-manager-dns01" + resource_group_name = var.resource_group_name + location = var.location +} + +# Creates Role Assignment +resource "azurerm_role_assignment" "dns_contributor" { + scope = var.dns_zone_id + role_definition_name = "DNS Zone Contributor" + principal_id = azurerm_user_assigned_identity.dns_identity.principal_id +} + +# Client Id Used for identity binding +output "identity_client_id" { + value = azurerm_user_assigned_identity.dns_identity.client_id +} + +# Resource Id Used for identity binding +output "identity_resource_id" { + value = azurerm_user_assigned_identity.dns_identity.id +} +``` + +Next we need to ensure we have installed [AAD Pod Identity](https://azure.github.io/aad-pod-identity) using their walk-through. This will install the CRDs and deployment required to assign the identity. + +Now we can create the identity resource and binding using the below manifest as an example: + +```yaml +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentity +metadata: + annotations: + # recommended to use namespaced identites https://azure.github.io/aad-pod-identity/docs/configure/match_pods_in_namespace/ + aadpodidentity.k8s.io/Behavior: namespaced + name: certman-identity + namespace: cert-manager # change to your preferred namespace +spec: + type: 0 # MSI + resourceID: # Resource Id From Previous step + clientID: # Client Id from previous step +--- +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentityBinding +metadata: + name: certman-id-binding + namespace: cert-manager # change to your preferred namespace +spec: + azureIdentity: certman-identity + selector: certman-label # This is the label that needs to be set on cert-manager pods +``` + +Next we need to ensure the cert-manager pod has a relevant label to use the pod identity binding. This can be done by editing the deployment and adding the below into the `.spec.template.metadata.labels` field + +```yaml +spec: + template: + metadata: + labels: + aadpodidbinding: certman-label # must match selector in AzureIdentityBinding +``` + +Or by using the helm values `podLabels` + +```yaml +podLabels: + aadpodidbinding: certman-label +``` + +Lastly when we create the certificate issuer we only need to specify the `hostedZoneName`, `resourceGroupName` and `subscriptionID` fields for the DNS zone. Example below: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + azureDNS: + subscriptionID: AZURE_SUBSCRIPTION_ID + resourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUP + hostedZoneName: AZURE_DNS_ZONE + # Azure Cloud Environment, default to AzurePublicCloud + environment: AzurePublicCloud +``` + +This authentication mechanism is what cert-manager considers 'ambient credentials'. Use of ambient credentials is disabled by default for cert-manager `Issuer`s. This to ensure unprivileged users who have permission to create issuers cannot issue certificates using any credentials cert-manager incidentally has access to. To enable this authentication mechanism for `Issuer`s, you will need to set `--issuer-ambient-credentials` flag on cert-manager controller to true. (There is a corresponding `--cluster-issuer-ambient-credentials` flag which is set to `true` by default). + +If you are using this authentication mechanism and ambient credentials are not enabled, you will see this error: +```bash +error instantiating azuredns challenge solver: ClientID is not set but neither --cluster-issuer-ambient-credentials nor --issuer-ambient-credentials are set. +``` + +These are necessary to enable Azure Managed Identities. + +## Managed Identity Using AKS Kubelet Identity + +When creating an AKS cluster in Azure there is the option to use a managed identity that is assigned to the kubelet. This identity is assigned to the underlying node pool in the AKS cluster and can then be used by the cert-manager pods to authenticate to Azure Active Directory. + +There are some caveats with this approach, these mainly being: + +- Any permissions granted to this identity will also be accessible to all containers running inside the Kubernetes cluster. +- Using AKS extensions like `Kube Dashboard`, `Virtual Node`, or `HTTP Application Routing` (see full list [here](https://docs.microsoft.com/en-us/azure/aks/use-managed-identity#summary-of-managed-identities)) will create additional identities that are assigned to your node pools. If your node pools have more than one identity assigned, you will need to specify either `clientID` or `resourceID` to select the correct one. + +To set this up, firstly you will need to retrieve the identity that the kubelet is using by querying the AKS cluster. This can then be used to create the appropriate permissions in the DNS zone. + +- Example commands using `azure-cli`: + +```bash +# Get AKS Kubelet Identity +PRINCIPAL_ID=$(az aks show -n $CLUSTERNAME -g $CLUSTER_GROUP --query "identityProfile.kubeletidentity.objectId" -o tsv) + +# Get existing DNS Zone Id +ZONE_ID=$(az network dns zone show --name $ZONE_NAME --resource-group $ZONE_GROUP --query "id" -o tsv) + +# Create role assignment +az role assignment create --role "DNS Zone Contributor" --assignee $PRINCIPAL_ID --scope $ZONE_ID +``` + +- Example terraform: + +```terraform +variable dns_zone_id {} + +# Creating the AKS cluster, abbreviated. +resource "azurerm_kubernetes_cluster" "cluster" { + ... + # Creates Identity associated to kubelet + identity { + type = "SystemAssigned" + } + ... +} + +resource "azurerm_role_assignment" "dns_contributor" { + scope = var.dns_zone_id + role_definition_name = "DNS Zone Contributor" + principal_id = azurerm_kubernetes_cluster.cluster.kubelet_identity[0].object_id + skip_service_principal_aad_check = true # Allows skipping propagation of identity to ensure assignment succeeds. +} +``` + +Then when creating the cert-manager issuer we need to specify the `hostedZoneName`, `resourceGroupName` and `subscriptionID` fields for the DNS Zone. + +We also need to specify `managedIdentity.clientID` or `managedIdentity.resourceID` if multiple managed identities are assigned to the node pools. + +The value for `managedIdentity.clientID` can be fetched by running this command: + +```bash +az aks show -n $CLUSTERNAME -g $CLUSTER_GROUP --query "identityProfile.kubeletidentity.clientId" -o tsv +``` + +Example below: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + azureDNS: + subscriptionID: AZURE_SUBSCRIPTION_ID + resourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUP + hostedZoneName: AZURE_DNS_ZONE + # Azure Cloud Environment, default to AzurePublicCloud + environment: AzurePublicCloud + # optional, only required if node pools have more than 1 managed identity assigned + managedIdentity: + # client id of the node pool managed identity (can not be set at the same time as resourceID) + clientID: YOUR_MANAGED_IDENTITY_CLIENT_ID + # resource id of the managed identity (can not be set at the same time as clientID) + # resourceID: YOUR_MANAGED_IDENTITY_RESOURCE_ID +``` + +## Service Principal + +Configuring the AzureDNS DNS01 Challenge for a Kubernetes cluster requires +creating a service principal in Azure. + +To create the service principal you can use the following script (requires +`azure-cli` and `jq`): + +```bash +# Choose a name for the service principal that contacts azure DNS to present +# the challenge. +$ AZURE_CERT_MANAGER_NEW_SP_NAME=NEW_SERVICE_PRINCIPAL_NAME +# This is the name of the resource group that you have your dns zone in. +$ AZURE_DNS_ZONE_RESOURCE_GROUP=AZURE_DNS_ZONE_RESOURCE_GROUP +# The DNS zone name. It should be something like domain.com or sub.domain.com. +$ AZURE_DNS_ZONE=AZURE_DNS_ZONE + +$ DNS_SP=$(az ad sp create-for-rbac --name $AZURE_CERT_MANAGER_NEW_SP_NAME --output json) +$ AZURE_CERT_MANAGER_SP_APP_ID=$(echo $DNS_SP | jq -r '.appId') +$ AZURE_CERT_MANAGER_SP_PASSWORD=$(echo $DNS_SP | jq -r '.password') +$ AZURE_TENANT_ID=$(echo $DNS_SP | jq -r '.tenant') +$ AZURE_SUBSCRIPTION_ID=$(az account show --output json | jq -r '.id') +``` + +For security purposes, it is appropriate to utilize RBAC to ensure that you +properly maintain access control to your resources in Azure. The service +principal that is generated by this tutorial has fine-grained access to ONLY the +DNS Zone in the specific resource group specified. It requires this permission +so that it can read/write the \_acme\_challenge TXT records to the zone. + +Lower the Permissions of the service principal. + +```bash +$ az role assignment delete --assignee $AZURE_CERT_MANAGER_SP_APP_ID --role Contributor +``` + +Give Access to DNS Zone. + +```bash +$ DNS_ID=$(az network dns zone show --name $AZURE_DNS_ZONE --resource-group $AZURE_DNS_ZONE_RESOURCE_GROUP --query "id" --output tsv) +$ az role assignment create --assignee $AZURE_CERT_MANAGER_SP_APP_ID --role "DNS Zone Contributor" --scope $DNS_ID +``` + +Check Permissions. As the result of the following command, we would like to see just one object in the permissions array with "DNS Zone Contributor" role. + +```bash +$ az role assignment list --all --assignee $AZURE_CERT_MANAGER_SP_APP_ID +``` + +A secret containing service principal password should be created on Kubernetes to facilitate presenting the challenge to Azure DNS. You can create the secret with the following command: + +```bash +$ kubectl create secret generic azuredns-config --from-literal=client-secret=$AZURE_CERT_MANAGER_SP_PASSWORD +``` + +Get the variables for configuring the issuer. + +```bash +$ echo "AZURE_CERT_MANAGER_SP_APP_ID: $AZURE_CERT_MANAGER_SP_APP_ID" +$ echo "AZURE_CERT_MANAGER_SP_PASSWORD: $AZURE_CERT_MANAGER_SP_PASSWORD" +$ echo "AZURE_SUBSCRIPTION_ID: $AZURE_SUBSCRIPTION_ID" +$ echo "AZURE_TENANT_ID: $AZURE_TENANT_ID" +$ echo "AZURE_DNS_ZONE: $AZURE_DNS_ZONE" +$ echo "AZURE_DNS_ZONE_RESOURCE_GROUP: $AZURE_DNS_ZONE_RESOURCE_GROUP" +``` + +To configure the issuer, substitute the capital cased variables with the values +from the previous script. You can get the subscription id from the Azure portal. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + azureDNS: + clientID: AZURE_CERT_MANAGER_SP_APP_ID + clientSecretSecretRef: + # The following is the secret we created in Kubernetes. Issuer will use this to present challenge to Azure DNS. + name: azuredns-config + key: client-secret + subscriptionID: AZURE_SUBSCRIPTION_ID + tenantID: AZURE_TENANT_ID + resourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUP + hostedZoneName: AZURE_DNS_ZONE + # Azure Cloud Environment, default to AzurePublicCloud + environment: AzurePublicCloud +``` diff --git a/content/v1.13-docs/configuration/acme/dns01/cloudflare.md b/content/v1.13-docs/configuration/acme/dns01/cloudflare.md new file mode 100644 index 0000000000..b3f4ded449 --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/cloudflare.md @@ -0,0 +1,108 @@ +--- +title: Cloudflare +description: 'cert-manager configuration: ACME DNS-01 challenges using Cloudflare DNS' +--- + +To use Cloudflare, you may use one of two types of tokens. **API Tokens** allow application-scoped keys bound to specific zones and permissions, while **API Keys** are globally-scoped keys that carry the same permissions as your account. + +**API Tokens** are recommended for higher security, since they have more restrictive permissions and are more easily revocable. + +## API Tokens + +Tokens can be created at **User Profile > API Tokens > API Tokens**. The following settings are recommended: + +- Permissions: + - `Zone - DNS - Edit` + - `Zone - Zone - Read` +- Zone Resources: + - `Include - All Zones` + +To create a new `Issuer`, first make a Kubernetes secret containing your new API token: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: cloudflare-api-token-secret +type: Opaque +stringData: + api-token: +``` + +Then in your `Issuer` manifest: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + apiTokenSecretRef: + name: cloudflare-api-token-secret + key: api-token +``` + +## API Keys + +API keys can be retrieved at **User Profile > API Tokens > API Keys > Global API Key > View**. + +To create a new `Issuer`, first make a Kubernetes secret containing your API key: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: cloudflare-api-key-secret +type: Opaque +stringData: + api-key: +``` + +Then in your `Issuer` manifest: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + cloudflare: + email: my-cloudflare-acc@example.com + apiKeySecretRef: + name: cloudflare-api-key-secret + key: api-key +``` + +## Troubleshooting + +### Actor `com.cloudflare.api.token.xxxx` requires permission `com.cloudflare.api.account.zone.list` to list zones +If you get the error that your token does not have the correct permission to list zones there can be 2 causes. +1. The token lacks the `Zone - Zone - Read` permission +2. cert-manager identified the wrong zone name for the domain due to DNS issues. + +In the case of the 2nd issue you will see an error like below: +``` +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Started 6s cert-manager Challenge scheduled for processing + Warning PresentError 3s (x2 over 3s) cert-manager Error presenting challenge: Cloudflare API Error for GET "/zones?name=" + Error: 0: Actor 'com.cloudflare.api.token.xxxx' requires permission 'com.cloudflare.api.account.zone.list' to list zones +``` + +In this case we recommend [changing your DNS01 self-check nameservers](./README.md#setting-nameservers-for-dns01-self-check). + +## `Cloudflare API error for POST "/zones//dns_records` generic error + +You might be hitting this as Cloudflare blocks the use of the API to update DNS records for the following TLDs: `.cf`, `.ga`, `.gq`, `.ml` and `.tk`. +This is discussed in the [Cloudflare Community](https://community.cloudflare.com/t/unable-to-update-ddns-using-api-for-some-tlds/167228). +We recommend using an alternative DNS provider when using these TLDs. \ No newline at end of file diff --git a/content/v1.13-docs/configuration/acme/dns01/digitalocean.md b/content/v1.13-docs/configuration/acme/dns01/digitalocean.md new file mode 100644 index 0000000000..4d6c155863 --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/digitalocean.md @@ -0,0 +1,46 @@ +--- +title: DigitalOcean +description: 'cert-manager configuration: ACME DNS-01 challenges using DigitalOcean DNS' +--- + +This provider uses a Kubernetes `Secret` resource to work. In the following +example, the `Secret` will have to be named `digitalocean-dns` and have a +sub-key `access-token` with the token in it. For example: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: digitalocean-dns +data: + # insert your DO access token here + access-token: "base64 encoded access-token here" + ``` + +The access token must have write access. + +To create a Personal Access Token, see [DigitalOcean documentation](https://docs.digitalocean.com/reference/api/create-personal-access-token/). + +Handy direct link: https://cloud.digitalocean.com/account/api/tokens/new + +To encode your access token into base64, you can use the following + +```bash +echo -n 'your-access-token' | base64 +``` + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + digitalocean: + tokenSecretRef: + name: digitalocean-dns + key: access-token +``` \ No newline at end of file diff --git a/content/v1.13-docs/configuration/acme/dns01/google.md b/content/v1.13-docs/configuration/acme/dns01/google.md new file mode 100644 index 0000000000..460239de80 --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/google.md @@ -0,0 +1,242 @@ +--- +title: Google CloudDNS +description: 'cert-manager configuration: ACME DNS-01 challenges using Google CloudDNS' +--- + +This guide explains how to set up an `Issuer`, or `ClusterIssuer`, to use Google +CloudDNS to solve DNS01 ACME challenges. It's advised you read the [DNS01 +Challenge Provider](./README.md) page first for a more general understanding of +how cert-manager handles DNS01 challenges. + +This guide assumes that your cluster is hosted on Google Cloud Platform (GCP) +and that you already have a domain set up with CloudDNS. + +You'll need to be using a **Public DNS Zone**, so that the ACME challenge checker +is able to access the DNS records that cert-manager will create. + +## Set up a Service Account + +cert-manager needs to be able to add records to CloudDNS in order to solve the +DNS01 challenge. To enable this, a GCP service account must be created with the +`dns.admin` role. + +> Note: For this guide the `gcloud` command will be used to set up the service +> account. Ensure that `gcloud` is using the correct project and zone before +> entering the commands. These steps could also be completed using the Cloud +> Console. + +```bash +PROJECT_ID=myproject-id +gcloud iam service-accounts create dns01-solver --display-name "dns01-solver" +``` + +In the command above, replace `myproject-id` with the ID of your project. + +```bash +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member serviceAccount:dns01-solver@$PROJECT_ID.iam.gserviceaccount.com \ + --role roles/dns.admin +``` + +> **Note**: The use of the `dns.admin` role in this example role is for convenience. +> If you want to ensure cert-manager runs under a least privilege service account, +> you will need to create a custom role with the following permissions: +> +> * `dns.resourceRecordSets.*` +> * `dns.changes.*` +> * `dns.managedZones.list` + +## Use Static Credentials + +Follow the instructions in the following sections to deploy cert-manager using +static credentials for the service account you created. You should rotate these +credentials periodically. + +### Create a Service Account Secret + +To access this service account, cert-manager uses a key stored in a Kubernetes +`Secret`. First, create a key for the service account and download it as a JSON +file, then create a `Secret` from this file. + +Keep the key file safe and do not share it, as it could be used to gain access +access to your cloud resources. The key file can be deleted once it has been +used to generate the `Secret`. + +If you did not create the service account `dns01-solver` before, you need to +create it first. + +```bash +gcloud iam service-accounts create dns01-solver +``` + +Replace instances of `$PROJECT_ID` with the ID of your project. +```bash +gcloud iam service-accounts keys create key.json \ + --iam-account dns01-solver@$PROJECT_ID.iam.gserviceaccount.com +kubectl create secret generic clouddns-dns01-solver-svc-acct \ + --from-file=key.json +``` + +> Note: If you have already added the `Secret` but get an error: `...due to +> error processing: error getting clouddns service account: secret "XXX" not +> found`, the `Secret` may be in the wrong namespace. If you're configuring a +> `ClusterIssuer`, move the `Secret` to the `Cluster Resource Namespace` which +> is `cert-manager` by default. If you're configuring an `Issuer`, the `Secret` +> should be stored in the same namespace as the `Issuer` resource. + +### Create an Issuer That Uses CloudDNS + +Next, create an `Issuer` (or `ClusterIssuer`) with a `cloudDNS` provider. An +example `Issuer` manifest can be seen below with annotations. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + cloudDNS: + # The ID of the GCP project + project: $PROJECT_ID + # This is the secret used to access the service account + serviceAccountSecretRef: + name: clouddns-dns01-solver-svc-acct + key: key.json +``` + +For more information about `Issuers` and `ClusterIssuers`, see +[Configuration](../../README.md). + +Once an `Issuer` (or `ClusterIssuer`) has been created successfully, a +`Certificate` can then be added to verify that everything works. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: default +spec: + secretName: example-com-tls + issuerRef: + # The issuer created previously + name: example-issuer + dnsNames: + - example.com + - www.example.com +``` + +For more details about `Certificates`, see [Usage](../../../usage/README.md). + +## GKE Workload Identity + +If you are deploying cert-manager into a [Google Container Engine (GKE) +cluster](https://cloud.google.com/kubernetes-engine/) with workload identity +enabled, you can leverage workload identity to avoid creating and managing +static service account credentials. The [workload identity +how-to](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) +provides more detail on how workload identity functions, but briefly workload +identity allows you to link a Google service accounts (GSA) to Kubernetes +service accounts (KSA). This GSA/KSA linking is two-way, i.e., you must +establish the link in GCP _and_ Kubernetes. Once configured, workload identity +allows Kubernetes pods running under a KSA to access the GCP APIs with the +permissions of the linked GSA. The workload identity how-to also provides +detailed instructions on how to enable workload identity in your GKE cluster. +The instructions in the following sections assume you are deploying cert-manager +to a GKE cluster with workload identity already enabled. + +### Enable Ambient Credential Usage + +'Ambient Credentials' are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the ClusterIssuer API object. When this flag is enabled Cert-Manager will access the GKE Metadata server for credentials. By default this is enabled for ClusterIssuer resources but is disabled for Issuer resources. To enable it for Issuer resources set the `--issuer-ambient-credentials` flag. + +### Link KSA to GSA in GCP + +The cert-manager component that needs to modify DNS records is the pod created +as part of the cert-manager deployment. The [standard methods for deploying +cert-manager to Kubernetes](../../../installation/README.md) create the +cert-manager deployment in the cert-manager namespace and its pod spec specifies +it runs under the cert-manager service account. To link the GSA you created +above to the cert-manager KSA in the cert-manager namespace in your GKE cluster, +run the following command. + +```bash +gcloud iam service-accounts add-iam-policy-binding \ + --role roles/iam.workloadIdentityUser \ + --member "serviceAccount:$PROJECT_ID.svc.id.goog[cert-manager/cert-manager]" \ + dns01-solver@$PROJECT_ID.iam.gserviceaccount.com +``` + +If your cert-manager pods are running under a different service account, replace +`goog[cert-manager/cert-manager]` with `goog[NAMESPACE/SERVICE_ACCOUNT]`, where +`NAMESPACE` is the namespace of the service account and `SERVICE_ACCOUNT` is the +name of the service account. + +### Link KSA to GSA in Kubernetes + +After deploying cert-manager, add the proper workload identity annotation to the +cert-manager service account. + +```bash +kubectl annotate serviceaccount --namespace=cert-manager cert-manager \ + "iam.gke.io/gcp-service-account=dns01-solver@$PROJECT_ID.iam.gserviceaccount.com" +``` + +Again, if your cert-manager pods are running under a different service account, +replace `--namespace=cert-manager cert-manager` with `--namespace=NAMESPACE +SERVICE_ACCOUNT`, where `NAMESPACE` is the namespace of the service account and +`SERVICE_ACCOUNT` is the name of the service account. + +If you are deploying cert-manager using its helm chart, you can use the +`serviceAccount.annotations` configuration parameter to add the above workload +identity annotation to the cert-manager KSA. + +### Create an Issuer That Uses CloudDNS + +Next, create an `Issuer` (or `ClusterIssuer`) with a `clouddns` provider. An +example `Issuer` manifest can be seen below with annotations. Note that the +issuer does not include a `serviceAccountSecretRef` property. Excluding this +instructs cert-manager to use the default credentials provided by GKE workload +identity. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + cloudDNS: + # The ID of the GCP project + project: $PROJECT_ID +``` + +For more information about `Issuers` and `ClusterIssuers`, see +[Configuration](../../README.md). + +Once an `Issuer` (or `ClusterIssuer`) has been created successfully, a +`Certificate` can then be added to verify that everything works. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: default +spec: + secretName: example-com-tls + issuerRef: + # The issuer created previously + name: example-issuer + dnsNames: + - example.com + - www.example.com +``` + +For more details about `Certificates`, see [Usage](../../../usage/README.md). diff --git a/content/v1.13-docs/configuration/acme/dns01/rfc2136.md b/content/v1.13-docs/configuration/acme/dns01/rfc2136.md new file mode 100644 index 0000000000..86e9dd32df --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/rfc2136.md @@ -0,0 +1,207 @@ +--- +title: RFC-2136 +description: 'cert-manager configuration: ACME DNS-01 challenges using RFC2136-compliant DNS providers' +--- + +The goal of this document is to provide a configuration overview of the various +facilities required to deploy cert-manager against a RFC2136 compliant DNS +server such as BIND `named`. This capability is also commonly known as “dynamic +DNS”. + +Unlike the peer of other cert-manager DNS integrations, `named` is a bit of a +“Swiss Army Knife” of domain name servers. Over the years, it has been highly +optimized to provide maximal vertical scalability for a single node, as well as +horizontal scalability with service provider interfaces. This flexibility makes +it impossible to go into every possible `named` deployment that a user may run +in to though. Instead, this document will try to make sure your server is ready +to accept requests from cert-manager using command line tools, then get on to +the making the two work together. + +## Transaction Signatures ⇒ TSIG + +Dynamic DNS updates are essentially server queries which otherwise might return +resource records (RRs). Since DNS servers are commonly exposed to the public +internet, being able to push an unauthenticated update to any server that +responds to queries would be immediately untenable. + +In the eyes of the `named` architects, the generic solution to this problem +space was twofold. The first is to require manual enablement of updates at a +zone level, such as `example.com`. In a naive network, there is no requirement +that zone updates have any security to them, and clients can be configured such +that they can provide updates without any authentication. An example of where +this is useful is for machines booting using DHCP, in this case the machines +know about themselves and the DNS server can be configured to accept updates +when they come from the address being configured. + +This clearly has limitations in situations such as cert-manager and the DNS01 +challenge. In this environment, a TXT RR must be created after coordination with +the ACME server. After negotiating with the ACME server, a the TXT RR that is +published on the domain validates that the domain is legitimately engaged with +the process of creating a certificate for it. In the bigger picture of DNS, this +means that an arbitrary actor (cert-manager, in this case) must be able to add +one of these KV mappings to the domain and delete it after the certificate has +been issued. `cert-manager` does not have a convenient physical characteristic +such as a DHCP allocation to validate it's requests. + +For cases like this, we need to be able to sign a request that is being sent to +the DNS server. We do that through TSIGs, or Transaction SIGnatures. + +### Configuration Step 1 - Set up your DNS server for secure dynamic updates + +There are many excellent tutorials on the net that walk through +preparing a basic `named` server for dynamic updates: + +- https://www.cyberciti.biz/faq/unix-linux-bind-named-configuring-tsig/ +- https://tomthorp.me/blog/using-tsig-enable-secure-zone-transfers-between-bind-9x-servers + +More complex `name` deployments will not use text files, but rather may use LDAP +or SQL for a database for resource records. An additional wrinkle is metadata +configuration, such as for zone metadata like enabling dynamic updates or access +control lists (ACLs) for a zone. There are too many configurations to go into +here, but you should be able to find the documentation to do so. + +Whatever your deployment is, the goal at this stage has nothing to do with +cert-manager and everything to do with a tool called `nsupdate` generating +updates signed with TSIG. Once this is out of the way, you can attack the +cert-manager configuration with far greater confidence. + +#### Using `nsupdate` + +Most paths to configuring BIND `named` will go through using `dnssec-keygen`. +This command-line tool generates a named private key that is used for signing +TSIG requests. When a request is signed, both the signature and the name of the +private key are attached to the request in an unencrypted form. In this manner, +when the request is received, the name of the private key can be used to by the +recipient to find the private key itself, build a new signature with it, and +compare the two for acceptance. + +Since there are dozens of ways to have your `named` server misconfigured, we’ll +use `nsupdate` to test that the server behaves as expected before we get there. +`https://debian-administration.org/article/591/Using_the_dynamic_DNS_editor_nsupdate` +is a solid breakdown of how to use the tool. + +To get started, we’ll simply run `nsupdate -k ` where `keyID` is the value +returned from `dnssec-keygen`. This will read the key from disk and provide a +command prompt to issue commands. In general, we want to write a simple TXT RR +and make sure we can delete it. + +```bash +$ nsupdate -k +update add www1.example.com 60 txt testing +send +… test here with `nslookup` +update delete www1.example.com txt +send +… test here with `nslookup` +``` + +Any failures to write, read or delete the record will mean that cert-manager +will not be able to do so either, no matter how well it is configured. + +### Configuration Step 2 - Set up cert-manager + +Now we get to the fun stuff, seeing everything work. Remember that we need to +set up the ACME DNS01 issuer and challenge mechanism as well as the `rfc2136` +provider. Since the documentation covers the other parts sufficiently, let’s +focus on the provider here. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + rfc2136: + nameserver:
        + tsigKeyName: + tsigAlgorithm: HMACSHA512 // should be matched to the algo you chose in `dnssec-keygen` + tsigSecretSecretRef: + name: + key: +``` + +For example: + +```yaml + rfc2136: + nameserver: 1.2.3.4:53 + tsigKeyName: example-com-secret + tsigAlgorithm: HMACSHA512 + tsigSecretSecretRef: + name: tsig-secret + key: tsig-secret-key +``` + +For this example configuration, we’ll need the following two commands. The +first, on your `named` server generates the key. Note how `example-com-secret` +is both in the `tsigKeyName` above and the `dnssec-keygen` command that follows. + +```bash +$ dnssec-keygen -r /dev/urandom -a HMAC-SHA512 -b 512 -n HOST example-com-secret +``` + +Also note how the `tsigAlgorithm` is provided in both the configuration and the +`keygen` command. They are listed at +`https://github.com/miekg/dns/blob/v1.0.12/tsig.go#L18-L23`. + +The second bit of configuration you need on the Kubernetes side is to create a +secret. Pulling the secret key string from the `.private` file generated +above, use the secret in the placeholder below: + +```bash +$ kubectl -n cert-manager create secret generic tsig-secret --from-literal=tsig-secret-key= +``` + +Note how the `tsig-secret` and `tsig-secret-key` match the configuration in the +`tsigSecretSecretRef` above. + +## Rate Limits + +The `rfc2136` provider waits until *all* nameservers to in your domain's SOA RR +respond with the same result before it contacts Let's Encrypt to complete the +challenge process. This is because the challenge server contacts a +non-authoritative DNS server that does a recursive query (a query for records it +does not maintain locally). If the servers in the SOA do not contain the correct +values, it's likely that the non-authoritative server will have bad information +as well, causing the request to go against rate limits and eventually locking +the process out. + +This process is in place to protect users from server misconfiguration creating +a more subtle lockout that persists after the server configuration has been +repaired. + +As documented elsewhere, it is prudent to fully debug configurations using the +ACME staging servers before using the production servers. The staging servers +have less aggressive rate limits, but the certificates they issue are not signed +with a root certificate trusted by browsers. + +## What’s next? + +This configuration so far will actually do nothing. You still have to request a +certificate as described [here](../../../usage/README.md). Once a certificate is +requested, the provider will begin processing the request. + +## Troubleshooting + +- Be sure that you have fully tested the DNS server updates using `nsupdate` + first. Ideally, this is done from a pod in the same namespace as the `rfc2136` + provider to ensure there are no firewall issues. +- The logs for the `cert-manager` pod are your friend. Additional logs can be + generated by adding the `--v=5` argument to the container launch. +- The TSIG key is encoded with `base64`, but the Kubernetes API server also + expects that key literals will be decoded before they are stored. In some + cases, a key must be double-encoded. (If you've tested using `nsupdate`, it's + pretty easy to spot when you are running into this.) +- Pay attention to the refresh time of the zone you are working with. For zones + with low traffic, it will not make a significant difference to reduce the + refresh time down to about five minutes while getting initial certificates. + Once the process is working, the beauty of `cert-manager` is it doesn't matter + if a renewal takes hours due to refresh times, it's all automated! +- Compared to the other providers that often use REST APIs to modify DNS RRs, + this provider can take a little longer. You can `watch kubectl certificate + yourcert` to get a display of what's going on. It's not uncommon for the process + to take five minutes in total. \ No newline at end of file diff --git a/content/v1.13-docs/configuration/acme/dns01/route53.md b/content/v1.13-docs/configuration/acme/dns01/route53.md new file mode 100644 index 0000000000..e441b3b0c3 --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/route53.md @@ -0,0 +1,259 @@ +--- +title: Route53 +description: 'cert-manager configuration: ACME DNS-01 challenges using Amazon AWS Route53 DNS' +--- + +This guide explains how to set up an `Issuer`, or `ClusterIssuer`, to use Amazon +Route53 to solve DNS01 ACME challenges. It's advised you read the [DNS01 +Challenge Provider](./README.md) page first for a more general understanding of +how cert-manager handles DNS01 challenges. + +> Note: This guide assumes that your cluster is hosted on Amazon Web Services +> (AWS) and that you already have a hosted zone in Route53. + +## Set up an IAM Role + +cert-manager needs to be able to add records to Route53 in order to solve the +DNS01 challenge. To enable this, create a IAM policy with the following +permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "route53:GetChange", + "Resource": "arn:aws:route53:::change/*" + }, + { + "Effect": "Allow", + "Action": [ + "route53:ChangeResourceRecordSets", + "route53:ListResourceRecordSets" + ], + "Resource": "arn:aws:route53:::hostedzone/*" + }, + { + "Effect": "Allow", + "Action": "route53:ListHostedZonesByName", + "Resource": "*" + } + ] +} +``` + +> Note: The `route53:ListHostedZonesByName` statement can be removed if you +> specify the (optional) `hostedZoneID`. You can further tighten the policy by +> limiting the hosted zone that cert-manager has access to (e.g. +> `arn:aws:route53:::hostedzone/DIKER8JEXAMPLE`). + +## Credentials + +You have two options for the set up - either create a user or a role and attach +that policy from above. Using a role is considered best practice because you do +not have to store permanent credentials in a secret. + +cert-manager supports two ways of specifying credentials: + +- explicit by providing an `accessKeyID` or an `accessKeyIDSecretRef`, and a `secretAccessKeySecretRef` +- or implicit (using [metadata + service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) + or [environment variables or credentials + file](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials). + +cert-manager also supports specifying a `role` to enable cross-account access +and/or limit the access of cert-manager. Integration with +[`kiam`](https://github.com/uswitch/kiam) and +[`kube2iam`](https://github.com/jtblin/kube2iam) should work out of the box. + + +## Cross Account Access + +Example: Account Y manages Route53 DNS Zones. Now you want cert-manager running in Account X (or many other accounts) to be able to manage records in Route53 zones hosted in Account Y. + +First, create a role with the permissions policy above (let's call the role `dns-manager`) +in Account Y, and attach a trust relationship like the one below. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::XXXXXXXXXXX:role/cert-manager" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` + +Bear in mind, that you won't be able to define this policy until `cert-manager` role on account Y is created. If you are setting this up using a configuration language, you may want to define principal as: + +```json +"Principal": { + "AWS": "XXXXXXXXXXX" + } +``` +And restrict it, in a future step, after all the roles are created. + +This allows the role `cert-manager` in Account X to assume the `dns-manager` role in Account Y to manage the Route53 DNS zones in Account Y. For more information visit the [official +documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html). + +Second, create the cert-manager role in Account X; this will be used as a credentials source for the cert-manager pods running in Account X. Attach to the role the following **permissions** policy: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": "arn:aws:iam::YYYYYYYYYYYY:role/dns-manager", + "Action": "sts:AssumeRole" + } + ] +} +``` + +And the following trust relationship (Add AWS `Service`s as needed): + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` + +## Creating an Issuer (or `ClusterIssuer`) + +Here is an example configuration for a `ClusterIssuer`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + ... + solvers: + + # example: cross-account zone management for example.com + # this solver uses ambient credentials (i.e. inferred from the environment or EC2 Metadata Service) + # to assume a role in a different account + - selector: + dnsZones: + - "example.com" + dns01: + route53: + region: us-east-1 + hostedZoneID: DIKER8JEXAMPLE # optional, see policy above + role: arn:aws:iam::YYYYYYYYYYYY:role/dns-manager + + # this solver handles example.org challenges + # and uses explicit credentials + - selector: + dnsZones: + - "example.org" + dns01: + route53: + region: eu-central-1 + # The AWS access key ID can be specified using the literal accessKeyID parameter + # or retrieved from a secret using the accessKeyIDSecretRef + # If using accessKeyID, omit the accessKeyIDSecretRef parameter and vice-versa + accessKeyID: AKIAIOSFODNN7EXAMPLE + accessKeyIDSecretRef: + name: prod-route53-credentials-secret + key: access-key-id + secretAccessKeySecretRef: + name: prod-route53-credentials-secret + key: secret-access-key + # you can also assume a role with these credentials + role: arn:aws:iam::YYYYYYYYYYYY:role/dns-manager +``` + +Note that, as mentioned above, the pod is using `arn:aws:iam::XXXXXXXXXXX:role/cert-manager` as a credentials source in Account X, but the `ClusterIssuer` ultimately assumes the `arn:aws:iam::YYYYYYYYYYYY:role/dns-manager` role to actually make changes in Route53 zones located in Account Y. + +## EKS IAM Role for Service Accounts (IRSA) + +While [`kiam`](https://github.com/uswitch/kiam) / [`kube2iam`](https://github.com/jtblin/kube2iam) work directly with cert-manager, some special attention is needed for using the [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) feature available on EKS. + +### OIDC provider + +First follow the AWS documentation [Enabling IAM roles for service accounts on your cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) to ensure that the OIDC provider for the EKS cluster is enabled. The OIDC information is needed to create the trust relationship for the cert-manager role below. + +### IAM role trust policy + +The cert-manager role needs the following trust relationship attached to the role in order to use the IRSA method. Replace the following: + +- `` with the AWS account ID of the EKS cluster. +- `` with the region where the EKS cluster is located. +- `` with the hash in the EKS API URL; this will be a random 32 character hex string (example: `45DABD88EEE3A227AF0FA468BE4EF0B5`) +- `` with the namespace where cert-manager is running. +- `` with the name of the `ServiceAccount` object created by cert-manager. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRoleWithWebIdentity", + "Principal": { + "Federated": "arn:aws:iam:::oidc-provider/oidc.eks..amazonaws.com/id/" + }, + "Condition": { + "StringEquals": { + "oidc.eks..amazonaws.com/id/:sub": "system:serviceaccount::" + } + } + } + ] +} +``` + +**Note:** If you're following the Cross Account example above, this trust policy is attached to the cert-manager role in Account X with ARN `arn:aws:iam::XXXXXXXXXXX:role/cert-manager`. The permissions policy is the same as above. + +### Service annotation + +Annotate the `ServiceAccount` created by cert-manager: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXX:role/cert-manager +``` + +You will also need to modify the cert-manager `Deployment` with the correct file system permissions, so the `ServiceAccount` token can be read. + +```yaml +spec: + template: + spec: + securityContext: + fsGroup: 1001 +``` + +The cert-manager Helm chart provides a variable for injecting annotations into cert-manager's `ServiceAccount` and `Deployment` object like so: + +```yaml +serviceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXX:role/cert-manager +securityContext: + fsGroup: 1001 +``` + +**Note:** If you're following the Cross Account example above, modify the `ClusterIssuer` in the same way as above with the role from Account Y. diff --git a/content/v1.13-docs/configuration/acme/dns01/webhook.md b/content/v1.13-docs/configuration/acme/dns01/webhook.md new file mode 100644 index 0000000000..d9a4339e39 --- /dev/null +++ b/content/v1.13-docs/configuration/acme/dns01/webhook.md @@ -0,0 +1,32 @@ +--- +title: Webhook +description: 'cert-manager configuration: ACME DNS-01 challenges using External Webhook Solvers' +--- + +The webhook `Issuer` is a generic ACME solver. The actual work is done by an +external service. Look at the respective documentation of +[`dns-providers`](../../../contributing/dns-providers.md). + +View more webhook solvers at https://github.com/topics/cert-manager-webhook. + +Here is an example of how webhook providers are to be configured. All `DNS01` +providers will contain their own specific configuration however all require a +`groupName` and `solverName` field. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + ... + solvers: + - dns01: + webhook: + groupName: $WEBHOOK_GROUP_NAME + solverName: $WEBHOOK_SOLVER_NAME + config: + ... + +``` diff --git a/content/v1.13-docs/configuration/acme/http01/README.md b/content/v1.13-docs/configuration/acme/http01/README.md new file mode 100644 index 0000000000..2e3e8242af --- /dev/null +++ b/content/v1.13-docs/configuration/acme/http01/README.md @@ -0,0 +1,393 @@ +--- +title: HTTP01 +description: 'cert-manager configuration: ACME HTTP-01 challenges' +--- + +
        + +📌 This page focuses on solving ACME HTTP-01 challenges. If you are looking for +how to automatically create Certificate resources by annotating Ingress or +Gateway resources, see [Securing Ingress Resources](../../../usage/ingress.md) and +[Securing Gateway Resources](../../../usage/gateway.md). + +
        + +cert-manager uses your existing Ingress or Gateway configuration in order to +solve HTTP01 challenges. + + +## Configuring the HTTP01 Ingress solver + +This page contains details on the different options available on the `Issuer` +resource's HTTP01 challenge solver configuration. For more information on +configuring ACME issuers and their API format, read the [ACME Issuers](../README.md) +documentation. + +You can read about how the HTTP01 challenge type works on the [Let's Encrypt +challenge types +page](https://letsencrypt.org/docs/challenge-types/#http-01-challenge). + +Here is an example of a simple `HTTP01` ACME issuer with more options for +configuration below: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: example-issuer +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + privateKeySecretRef: + name: example-issuer-account-key + solvers: + - http01: + ingress: + ingressClassName: nginx +``` + +## Options + +The HTTP01 Issuer supports a number of additional options. For full details on +the range of options available, read the [reference +documentation](../../../reference/api-docs.md#acme.cert-manager.io/v1.ACMEChallengeSolverHTTP01). + +### `ingressClassName` + +
        + +📌 The field `ingressClassName` was added in cert-manager 1.12. + +
        + +If the `ingressClassName` field is specified, cert-manager will create new +`Ingress` resources in order to route traffic to the `acmesolver` pods, which +are responsible for responding to ACME challenge validation requests. + +This is the recommended way of configuring the Ingress controller. Most Ingress +controllers support `ingressClassName`, with the notable exception of +ingress-gce (as per the page [Configure Ingress for external load +balancing](https://cloud.google.com/kubernetes-engine/docs/how-to/load-balance-ingress)). + +### `class` + +If the `class` field is specified, a new Ingress resource with a randomly +generated name will be created in order to solve the challenge. This new +resource will have an annotation with key `kubernetes.io/ingress.class` and +value set to the value of the `class` field. + +This field is only recommended with ingress-gce. ingress-gce [doesn't support the +`ingressClassName` field](https://cloud.google.com/kubernetes-engine/docs/how-to/load-balance-ingress). + +### `name` + +If the `name` field is specified, cert-manager will edit the named +ingress resource in order to solve HTTP01 challenges. + +This is useful for compatibility with ingress controllers such as `ingress-gce`, +which utilize a unique IP address for each `Ingress` resource created. + +This mode should be avoided when using ingress controllers that expose a single +IP for all ingress resources, as it can create compatibility problems with +certain ingress-controller specific annotations. + +
        + +If `class` and `ingressClassName` are not specified, and `name` is also not +specified, cert-manager will default to create *new* `Ingress` resources but +will **not** set the ingress class on these resources, meaning *all* ingress +controllers installed in your cluster will serve traffic for the challenge +solver, potentially incurring additional cost. + +
        + + +### `serviceType` + +In rare cases it might be not possible/desired to use `NodePort` as type for the +HTTP01 challenge response service, e.g. because of Kubernetes limit +restrictions. To define which Kubernetes service type to use during challenge +response specify the following HTTP01 configuration: + +```yaml + http01: + ingress: + # Valid values are ClusterIP and NodePort + serviceType: ClusterIP +``` + +By default, type `NodePort` will be used when you don't set HTTP01 or when you set +`serviceType` to an empty string. Normally there's no need to change this. + + +### `podTemplate` + +You may wish to change or add to the labels and annotations of solver pods. +These can be configured under the `metadata` field under `podTemplate`. + +Similarly, you can set the `nodeSelector`, tolerations and affinity of solver +pods by configuring under the `spec` field of the `podTemplate`. No other +spec fields can be edited. + +An example of how you could configure the template is as so: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ... +spec: + acme: + server: ... + privateKeySecretRef: + name: ... + solvers: + - http01: + ingress: + podTemplate: + metadata: + labels: + foo: "bar" + env: "prod" + spec: + nodeSelector: + bar: baz +``` + +The added labels and annotations will merge on top of the cert-manager defaults, +overriding entries with the same key. + +No other fields of the `podTemplate` exist. + +### `ingressTemplate` + +It is possible to add labels and annotations to the solver ingress resources. +It can be really useful when you are managing several Ingress Controllers across your cluster and you want to make sure that the right one will pick up and expose the solver (for the upcoming challenge to resolve). +These can be configured under the `metadata` field under `ingressTemplate`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ... +spec: + acme: + server: ... + privateKeySecretRef: + name: ... + solvers: + - http01: + ingress: + ingressTemplate: + metadata: + labels: + foo: "bar" + annotations: + "nginx.ingress.kubernetes.io/whitelist-source-range": "0.0.0.0/0,::/0" + "nginx.org/mergeable-ingress-type": "minion" + "traefik.ingress.kubernetes.io/frontend-entry-points": "http" +``` + +The added labels and annotations will merge on top of the cert-manager defaults, +overriding entries with the same key. + +No other fields of the ingress can be edited. + +## Configuring the HTTP-01 Gateway API solver + +**FEATURE STATE**: cert-manager 1.5 [alpha] + +The Gateway and HTTPRoute resources are part of the [Gateway API][gwapi], a set +of CRDs that you install on your Kubernetes cluster that provide various +improvements over the Ingress API. + +[gwapi]: https://gateway-api.sigs.k8s.io + +
        + +📌 This feature requires the installation of the [Gateway API bundle](https://gateway-api.sigs.k8s.io/guides/#installing-a-gateway-controller) and passing a +feature flag to the cert-manager controller. + +To install v1.5.1 Gateway API bundle (Gateway CRDs and webhook), run the following command: + +```sh +kubectl apply -f "https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.5.1/standard-install.yaml" +``` + +To enable the feature in cert-manager, turn on the `GatewayAPI` feature gate: + +- If you are using Helm: + + ```sh + helm upgrade --install cert-manager jetstack/cert-manager --namespace cert-manager \ + --set "extraArgs={--feature-gates=ExperimentalGatewayAPISupport=true}" + ``` + +- If you are using the raw cert-manager manifests, add the following flag to the + cert-manager controller Deployment: + + ```yaml + args: + - --feature-gates=ExperimentalGatewayAPISupport=true + ``` + +The Gateway API CRDs should either be installed before cert-manager starts or +the cert-manager Deployment should be restarted after installing the Gateway API +CRDs. This is important because some of the cert-manager components only perform +the Gateway API check on startup. You can restart cert-manager with the +following command: + +```sh +kubectl rollout restart deployment cert-manager -n cert-manager +``` + +
        + + +
        + +🚧 cert-manager 1.8+ is tested with v1alpha2 Kubernetes Gateway API. It should also work +with v1beta1 because of resource conversion, but has not been tested with it. +
        + +The Gateway API HTTPRoute HTTP-01 solver creates a temporary HTTPRoute using the +given labels. These labels must match a Gateway that contains a listener on port +80. + +Here is an example of a HTTP-01 ACME Issuer using the Gateway API: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt + namespace: default +spec: + acme: + solvers: + - http01: + gatewayHTTPRoute: + parentRefs: + - name: traefik + namespace: traefik + kind: Gateway +``` + +The Issuer relies on an existing Gateway present on the cluster. cert-manager +does not edit Gateway resources. + +For example, the following Gateway will allow the Issuer to solve the challenge: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: Gateway +metadata: + name: traefik + namespace: traefik +spec: + gatewayClassName: traefik + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +``` + +In the above example, the Gateway has been specifically created for the purpose +of solving HTTP-01 challenges, but you can also choose to re-use your existing +Gateway, as long as it has a listener on port 80. + +The `labels` on your Issuer may reference a Gateway that is on a separate +namespace, as long as the Gateway's port 80 listener is configured with `from: +All`. Note that the Certificate will still be created on the same namespace as +the Issuer, which means that you won't be able to reference this Secret in the +above-mentioned Gateway. + +When the above Issuer is presented with a Certificate, cert-manager creates the +temporary HTTPRoute. For example, with the following Certificate: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-tls + namespace: default +spec: + issuerRef: + name: letsencrypt + dnsNames: + - example.net +``` + +You will see an HTTPRoute appear: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: HTTPRoute +metadata: + name: cm-acme-http-solver-gdhvg + namespace: default +spec: + parentRefs: + - name: traefik + namespace: traefik + kind: Gateway + hostnames: + - example.net + rules: + - forwardTo: + - port: 8089 + serviceName: cm-acme-http-solver-gdhvg + weight: 1 + matches: + - path: + type: Exact + value: /.well-known/acme-challenge/YadC4gaAzqEPU1Yea0D2MrzvNRWiBCtUizCtpiRQZqI +``` + +After the Certificate is issued, the HTTPRoute is deleted. + + +### `labels` + +These labels are copied into the temporary HTTPRoute created by cert-manager for +solving the HTTP-01 challenge. These labels must match one of the Gateway +resources on your cluster. The matched Gateway have a listener on port 80. + +Note that when the labels do not match any Gateway on your cluster, cert-manager +will create the temporary HTTPRoute challenge and nothing will happen. + + +### `serviceType` + +This field has the same meaning as the +[`http01.ingress.serviceType`](#ingress-service-type). + + +## Setting Nameservers for HTTP-01 solver propagation checks + +cert-manager will perform reachability tests before attempting a HTT01 +challenge. By default cert-manager will use the recursive nameservers taken +from `/etc/resolv.conf` to query the challenge URL. + +If this is not desired (for example with split-horizon DNS), the cert-manager +controller exposes a flag that allows you alter this behavior: + +`--acme-http01-solver-nameservers` Comma separated string with host and port of the +recursive nameservers cert-manager should query. + + +Example usage: +```bash +--acme-http01-solver-nameservers="8.8.8.8:53,1.1.1.1:53" +``` + +If you're using the `cert-manager` helm chart, you can set recursive nameservers +through `.Values.extraArgs` or at the command at helm install/upgrade time +with `--set`: + +```bash +--set 'extraArgs={--acme-http01-solver-nameservers=8.8.8.8:53\,1.1.1.1:53}' +``` diff --git a/content/v1.13-docs/configuration/acme/http01/externalloadbalancer.md b/content/v1.13-docs/configuration/acme/http01/externalloadbalancer.md new file mode 100644 index 0000000000..a85966bd29 --- /dev/null +++ b/content/v1.13-docs/configuration/acme/http01/externalloadbalancer.md @@ -0,0 +1,34 @@ +--- +title: External Load Balancer +description: 'cert-manager configuration: ACME HTTP-01 challenges using External Load Balancers' +--- + +When you are using an external load balancer provided by any host, you can face several configuration issues to get it work with cert-manager. + +This documentation is intended to help configure the HTTP-01 challenge type for instances behind external load balancer. + +## NAT Loopback / Hairpin + +The first configuration point is NAT loopback. You can face check issues due to Load Balancer preventing instances behind it to access its external interface. + +Some Network Load Balancer have this kind of limitation for several reasons. It can be configured through `iptables` rerouting configuration known as `NAT loopback`. + +To check if you are facing this problem : + +1. Check that the endpoint of the challenge is accessible to the public : `curl ` +2. Check that the challenge endpoint is NOT accessible from inside behind the Load Balancer: use SSH to open a session on a node places behind the LB; then launch the same command than before : `curl ` + +The `HTTP-01` challenge's endpoint can be found in the logs when the `pre-check` fails. If it does not appear in the logs, you can check the challenge URL by `kubectl`command. + +`` is the URL used to test the HTTP-01 from the certificate `Issuer`. For Let's Encrypt for example, the URL is formed like `/.well-known/acme-challenge/` + + +## Load Balancer HTTP endpoints + +If you are using a Load Balancer (outside a managed Kubernetes service), you should be able to configure the Load Balancer protocol as HTTP, HTTPS, TCP, UDP. Several Load Balancer now offer free TLS certificates with Let's Encrypt. + +When using HTTP(s) protocols for your Load Balancer, it can intercept the challenge URL to replace the response's verification hash with their hash. + +In this case, cert-manager will fail `did not get expected response when querying endpoint, expected 'xxxx' but got: yyyy (truncated)`. + +This kind of error can be thrown for multiple reasons. This case shows a correctly formatted response, but not the expected one. The solution is to configure the Load Balancer with TCP protocol so that the HTTP request will not be intercepted by the host. \ No newline at end of file diff --git a/content/v1.13-docs/configuration/ca.md b/content/v1.13-docs/configuration/ca.md new file mode 100644 index 0000000000..fffb6f2401 --- /dev/null +++ b/content/v1.13-docs/configuration/ca.md @@ -0,0 +1,94 @@ +--- +title: CA +description: 'cert-manager configuration: CA Issuers' +--- + +⚠️ CA issuers are generally either for trying cert-manager out or else for advanced users with +a good idea of how to run a PKI. To be used safely in production, CA issuers introduce complex +planning requirements around rotation, trust store distribution and disaster recovery. + +If you're not planning to run your own PKI, use a different issuer type. + +The CA issuer represents a Certificate Authority whose certificate and +private key are stored inside the cluster as a Kubernetes `Secret`. + +Certificates issued by a CA issuer will not be publicly trusted and so are unlikely to be trusted +by your applications without further configuration. + +Consider [trust-manager](../projects/trust-manager/README.md) for distributing your CA certificate safely +across your cluster! + +## Deployment + +CA Issuers must be configured with a certificate and private key stored in a Kubernetes +secret. You can create this externally if you wish, or you could bootstrap a root certificate +using a [`SelfSigned` issuer](./selfsigned.md#bootstrapping-ca-issuers). + +Your certificate's secret should reside in the same namespace as the `Issuer`, or otherwise +in the `Cluster Resource Namespace` in the case of a `ClusterIssuer`. + +The `Cluster Resource Namespace` is defaulted as being the `cert-manager` namespace, but +can be configured using the `--cluster-resource-namespace` flag on the cert-manager controller. + +Below is an example of a secret resource that will be used for signing. Take +note of the index keys used for each field as these are required in order for +cert-manager to find the certificate and key. Also note that, like all secrets, +data must be base64 encoded. The command `$ cat crt.pem | base64 -w0` should help you +on GNU-based systems (Debian, Ubuntu, etc.) and `$ cat crt.pem | base64 -b0` on BSD-based +systems (most notably macOS). + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: ca-key-pair + namespace: sandbox +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMrVENDQWVHZ0F3SUJBZ0lKQUtQR3dLRGwvNUhuTUEwR0NTcUdTSWIzRFFFQkN3VUFNQk14RVRBUEJnTlYKQkFNTUNHcHZjMmgyWVc1c01CNFhEVEU1TURneU1qRTJNRFUxT0ZvWERUSTVNRGd4T1RFMk1EVTFPRm93RXpFUgpNQThHQTFVRUF3d0lhbTl6YUhaaGJtd3dnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCCkFRQ3doU0IvcVc2L2tMYjJ6cHUrRUp2RDl3SEZhcStRQS8wSkgvTGxseW83ekFGeCtISHErQ09BYmsrQzhCNHQKL0hVRXNuczVSTDA5Q1orWDRqNnBiSkZkS2R1UHhYdTVaVllua3hZcFVEVTd5ZzdPU0tTWnpUbklaNzIzc01zMApSNmpZbi9Ecmo0eFhNSkVmSFVEcVllU1dsWnIzcWkxRUZhMGM3ZlZEeEgrNHh0WnROTkZPakg3YzZEL3ZXa0lnCldRVXhpd3Vzc2U2S01PV2pEbnYvNFZyamVsMlFnVVlVYkhDeWVaSG1jdGkrSzBMV0Nmby9SZzZQdWx3cmJEa2gKam1PZ1l0MzBwZGhYME9aa0F1a2xmVURIZnA4YmpiQ29JMnRhWUFCQTZBS2pLc08zNUxBRVU3OUNMMW1MVkh1WgpBQ0k1VWppamEzVlBXVkhTd21KUEp5dXhBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlFtbDVkVEFaaXhGS2hqCjkzd3VjUldoYW8vdFFqQWZCZ05WSFNNRUdEQVdnQlFtbDVkVEFaaXhGS2hqOTN3dWNSV2hhby90UWpBTUJnTlYKSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFCK2tsa1JOSlVLQkxYOHlZa3l1VTJSSGNCdgpHaG1tRGpKSXNPSkhac29ZWGRMbEcxcFpORmpqUGFPTDh2aDQ0Vmw5OFJoRVpCSHNMVDFLTWJwMXN1NkNxajByClVHMWtwUkJlZitJT01UNE1VN3ZSSUNpN1VPbFJMcDFXcDBGOGxhM2hQT2NSYjJ5T2ZGcVhYeVpXWGY0dDBCNDUKdEhpK1pDTkhCOUZ4alNSeWNiR1lWaytUS3B2aEphU1lOTUdKM2R4REthUDcrRHgzWGNLNnNBbklBa2h5SThhagpOVSttdzgvdG1Sa1A0SW4va1hBUitSaTBxVW1Iai92d3ZuazRLbTdaVXkxRllIOERNZVM1TmtzbisvdUhsUnhSClY3RG5uMDM5VFJtZ0tiQXFONzJnS05MbzVjWit5L1lxREFZSFlybjk4U1FUOUpEZ3RJL0svQVRwVzhkWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBc0lVZ2Y2bHV2NUMyOXM2YnZoQ2J3L2NCeFdxdmtBUDlDUi95NVpjcU84d0JjZmh4CjZ2Z2pnRzVQZ3ZBZUxmeDFCTEo3T1VTOVBRbWZsK0krcVd5UlhTbmJqOFY3dVdWV0o1TVdLVkExTzhvT3praWsKbWMwNXlHZTl0N0RMTkVlbzJKL3c2NCtNVnpDUkh4MUE2bUhrbHBXYTk2b3RSQld0SE8zMVE4Ui91TWJXYlRUUgpUb3grM09nLzcxcENJRmtGTVlzTHJMSHVpakRsb3c1Ny8rRmE0M3Bka0lGR0ZHeHdzbm1SNW5MWXZpdEMxZ242ClAwWU9qN3BjSzJ3NUlZNWpvR0xkOUtYWVY5RG1aQUxwSlgxQXgzNmZHNDJ3cUNOcldtQUFRT2dDb3lyRHQrU3cKQkZPL1FpOVppMVI3bVFBaU9WSTRvMnQxVDFsUjBzSmlUeWNyc1FJREFRQUJBb0lCQUNFTkhET3JGdGg1a1RpUApJT3dxa2UvVVhSbUl5MHlNNHFFRndXWXBzcmUxa0FPMkFDWjl4YS96ZDZITnNlanNYMEM4NW9PbmtrTk9mUHBrClcxVS94Y3dLM1ZpRElwSnBIZ09VNzg1V2ZWRXZtU3dZdi9Fb1V3eHFHRVMvcnB5Z1drWU5WSC9XeGZGQlg3clMKc0dmeVltbXJvM09DQXEyLzNVVVFiUjcrT09md3kzSHdUdTBRdW5FSnBFbWU2RXdzdWIwZzhTTGp2cEpjSHZTbQpPQlNKSXJyL1RjcFRITjVPc1h1Vm5FTlVqV3BBUmRQT1NrRFZHbWtCbnkyaVZURElST3NGbmV1RUZ1NitXOWpqCmhlb1hNN2czbkE0NmlLenUzR0YwRWhLOFkzWjRmeE42NERkbWNBWnphaU1vMFJVaktWTFVqbVlQSEUxWWZVK3AKMkNYb3dNRUNnWUVBMTgyaU52UEkwVVlWaUh5blhKclNzd1YrcTlTRStvVi90U2ZSUUNGU2xsV0d3KzYyblRiVwpvNXpoL1RDQW9VTVNSbUFPZ0xKWU1LZUZ1SWdvTEoxN1pvWjN0U1czTlVtMmRpT0lPSHorcTQxQzM5MDRrUzM5CjkrYkFtVmtaSFA5VktLOEMraS9tek5mSkdHZEJadGIweWtTM2t3OUIxTHdnT3o3MDhFeXFSQ2tDZ1lFQTBXWlAKbzF2MThnV2tMK2FnUDFvOE13eDRPZlpTN3dKY3E0Z0xnUWhjYS9pSkttY0x0RFN4cUJHckJ4UVo0WTIyazlzdQpzTFVrNEJobGlVM29iUUJNaUdtMGtITHVBSEFRNmJvdWZBMUJwZjN2VFdHSkhSRjRMeFJsNzc2akw4UXI4VnpxClpURVBtY0R0T0hpYjdwb2I1Z2IzSDhiVGhYeUhmdGZxRW55alhFa0NnWUVBdk9DdDZZclZhTlQrWThjMmRFYk4Kd3dJOExBaUZtdjdkRjZFUjlCODJPWDRCeGR0WTJhRDFtNTNqN2NaVnpzNzFYOE1TN25FcDN1dkFqaElkbDI3KwpZbTJ1dUUyYVhIbDN5VTZ3RzBETFpUcnVIU0Z5TVI4ZithbHRTTXBDd0s1NXluSGpHVFp6dXpYaVBBbWpwRzdmCk1XbVRncE1IK3puc3UrNE9VNFBHUW9FQ2dZQWNqdUdKbS84YzlOd0JsR2lDZTJIK2JGTHhSTURteStHcm16QkcKZHNkMENqOWF3eGI3aXJ3MytjRGpoRUJMWExKcjA5YTRUdHdxbStrdElxenlRTG92V0l0QnNBcjVrRThlTVVBcAp0djBmRUZUVXJ0cXVWaldYNWlaSTNpMFBWS2ZSa1NSK2pJUmVLY3V3aWZKcVJpWkw1dU5KT0NxYzUvRHF3Yk93CnRjTHAwUUtCZ0VwdEw1SU10Sk5EQnBXbllmN0F5QVBhc0RWRE9aTEhNUGRpL2dvNitjSmdpUmtMYWt3eUpjV3IKU25QSG1TbFE0aEluNGMrNW1lbHBDWFdJaklLRCtjcTlxT2xmQmRtaWtYb2RVQ2pqWUJjNnVGQ1QrNWRkMWM4RwpiUkJQOUNtWk9GL0hOcHN0MEgxenhNd1crUHk5Q2VnR3hhZ0ZCekxzVW84N0xWR2h0VFFZCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== +``` + +> Note: If your issuer represents an intermediate, ensure that `tls.crt` contains +> the issuer's full chain in the correct order: `issuer -> intermediate(s) -> root`. +> The root (self-signed) CA certificate is optional, but adding it will ensure that +> the correct CA certificate is stored in the secrets for issued `Certificate`s under +> the `ca.crt` key. If you fail to provide a complete chain, it might not be possible +> for consumers of issued `Certificate`s to verify whether they're trusted. + +Next is to deploy the CA issuer which references this `Secret`. This is done by +referencing the secret name under the `ca` stanza in the `Issuer` spec. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ca-issuer + namespace: sandbox +spec: + ca: + secretName: ca-key-pair +``` + +Optionally, you can specify [CRL](https://en.wikipedia.org/wiki/Certificate_revocation_list) Distribution Points; an array of strings each of which identifies the location of the CRL from which the revocation of this certificate can be checked. + +```yaml +... +spec: + ca: + secretName: ca-key-pair + crlDistributionPoints: + - "http://example.com" +``` + +Once deployed, you can then check that the issuer has been successfully +configured by checking the ready status of the certificate. Replace `issuers` +here with `clusterissuers` if that is what has been deployed. + +```bash +$ kubectl get issuers ca-issuer -n sandbox -o wide +NAME READY STATUS AGE +ca-issuer True Signing CA verified 2m +``` + +Certificates are now ready to be requested by using the CA `Issuer` named +`ca-issuer` within the `sandbox` namespace. diff --git a/content/v1.13-docs/configuration/external.md b/content/v1.13-docs/configuration/external.md new file mode 100644 index 0000000000..d4bc78088f --- /dev/null +++ b/content/v1.13-docs/configuration/external.md @@ -0,0 +1,51 @@ +--- +title: External +description: 'cert-manager configuration: External Issuers' +--- + +cert-manager supports external `Issuer` types. While external issuers are not +implemented in the main cert-manager repository, they are otherwise treated the +same as any other issuer. + +External issuers are typically deployed as a pod which is configured +to watch for `CertificateRequest` resources in the cluster whose `issuerRef` +matches the name of the issuer. External issuers exist outside of the +`cert-manager.io` group. + +Installation for each issuer may differ; check the documentation for each +external issuer for more details on installing, configuring and using it. + +## Known External Issuers + +If you've created an external issuer which you'd like to share, +[raise a Pull Request](https://github.com/cert-manager/website/pulls) to have +it added here! + +These external issuers are known to support and honor [approval](https://cert-manager.io/docs/concepts/certificaterequest/#approval). + +- [kms-issuer](https://github.com/Skyscanner/kms-issuer): Requests + certificates signed using an [AWS KMS](https://aws.amazon.com/kms/) asymmetric key. +- [aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer): Requests + certificates from [AWS Private Certificate Authority](https://aws.amazon.com/certificate-manager/private-certificate-authority/) + for cloud native/hybrid environments. +- [google-cas-issuer](https://github.com/jetstack/google-cas-issuer): Used + to request certificates signed by private CAs managed by the + [Google Cloud Certificate Authority Service](https://cloud.google.com/certificate-authority-service/). +- [origin-ca-issuer](https://github.com/cloudflare/origin-ca-issuer): Used + to request certificates signed by + [Cloudflare Origin CA](https://developers.cloudflare.com/ssl/origin-configuration/origin-ca) + to enable TLS between Cloudflare edge and your Kubernetes workloads. +- [step-issuer](https://github.com/smallstep/step-issuer): Requests + certificates from the [Smallstep](https://smallstep.com) [Certificate Authority server](https://github.com/smallstep/certificates). +- [freeipa-issuer](https://github.com/guilhem/freeipa-issuer): Requests + certificates signed by [FreeIPA](https://www.freeipa.org). +- [ADCS Issuer](https://github.com/nokia/adcs-issuer): Requests + certificates signed by [Microsoft Active Directory Certificate Service](https://docs.microsoft.com/en-us/windows-server/networking/core-network-guide/cncg/server-certs/install-the-certification-authority). + [NOT MAINTAINED] +- [CFSSL Issuer](https://gerrit.wikimedia.org/r/plugins/gitiles/operations/software/cfssl-issuer/): Request certificates signed by a [CFSSL](https://github.com/cloudflare/cfssl) `multirootca` instance. +- [ncm-issuer](https://github.com/nokia/ncm-issuer): Requests certificates from the [Nokia](https://www.nokia.com/) [Netguard Certificate Manager](https://www.nokia.com/networks/security-portfolio/netguard/certificate-manager) +- [tcs-issuer](https://github.com/intel/trusted-certificate-issuer) Requests certificates signed securely using [Intel's SGX technology](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/overview.html). + +## Building New External Issuers + +If you're interested in building a new external issuer, check the [development documentation](../contributing/external-issuers.md). diff --git a/content/v1.13-docs/configuration/selfsigned.md b/content/v1.13-docs/configuration/selfsigned.md new file mode 100644 index 0000000000..bfc2b39b2b --- /dev/null +++ b/content/v1.13-docs/configuration/selfsigned.md @@ -0,0 +1,171 @@ +--- +title: SelfSigned +description: 'cert-manager configuration: SelfSigned Issuers' +--- + +⚠️ `SelfSigned` issuers are generally useful for bootstrapping a PKI locally, which +is a complex topic for advanced users. To be used safely in production, running a PKI +introduces complex planning requirements around rotation, trust store distribution and disaster recovery. + +If you're not planning to run your own PKI, use a different issuer type. + +The `SelfSigned` issuer doesn't represent a certificate authority as such, but +instead denotes that certificates will "sign themselves" using a given private +key. In other words, the private key of the certificate will be used to sign +the certificate itself. + +This `Issuer` type is useful for bootstrapping a root certificate for a +custom PKI (Public Key Infrastructure), or for otherwise creating simple +ad-hoc certificates for a quick test. + +There are important [caveats](#caveats) - including security issues - to +consider with `SelfSigned` issuers; in general you'd likely want to use a +[`CA`](./ca.md) issuer rather than a `SelfSigned` issuer. That said, +`SelfSigned` issuers are really useful for initially [bootstrapping](#bootstrapping-ca-issuers) +a `CA` issuer. + +> Note: a `CertificateRequest` that references a self-signed certificate _must_ +> also contain the `cert-manager.io/private-key-secret-name` annotation since +> the private key corresponding to the `CertificateRequest` is required to +> sign the certificate. This annotation is added automatically by the +> `Certificate` controller. + +## Deployment + +Since the `SelfSigned` issuer has no dependency on any other resource, it is +the simplest to configure. Only the `SelfSigned` stanza is required to be +present in the issuer spec, with no other configuration required: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: sandbox +spec: + selfSigned: {} +``` + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-cluster-issuer +spec: + selfSigned: {} +``` + +Once deployed, you should be able to see immediately that the issuer is ready +for signing: + +```bash +$ kubectl get issuers -n sandbox -o wide selfsigned-issuer +NAME READY STATUS AGE +selfsigned-issuer True 2m + +$ kubectl get clusterissuers -o wide selfsigned-cluster-issuer +NAME READY STATUS AGE +selfsigned-cluster-issuer True 3m +``` + +### Bootstrapping `CA` Issuers + +One of the ideal use cases for `SelfSigned` issuers is to bootstrap a custom +root certificate for a private PKI, including with the cert-manager [`CA`](./ca.md) +issuer. + +The YAML below will create a `SelfSigned` issuer, issue a root certificate and +use that root as a `CA` issuer: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: sandbox +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: my-selfsigned-ca + namespace: sandbox +spec: + isCA: true + commonName: my-selfsigned-ca + secretName: root-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + group: cert-manager.io +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: my-ca-issuer + namespace: sandbox +spec: + ca: + secretName: root-secret +``` + +### CRL Distribution Points + +You may also optionally specify [CRL](https://en.wikipedia.org/wiki/Certificate_revocation_list) +Distribution Points as an array of strings, each of which identifies the location of a CRL in +which the revocation status of issued certificates can be checked: + +```yaml +... +spec: + selfSigned: + crlDistributionPoints: + - "http://example.com" +``` + +## Caveats + +### Trust + +Clients consuming `SelfSigned` certificates have _no way_ to trust them +without already having the certificates beforehand, which can be hard to +manage when the client is in a different namespace to the server. + +This limitation can be tackled by using [trust-manager](../projects/trust-manager/README.md) to distribute `ca.crt` +to other namespaces. + +There is no secure alternative to solving the problem of distributing trust stores; it's possible +to "TOFU" (trust-on-first-use) a certificate, but that approach is vulnerable to man-in-the-middle attacks. + +### Certificate Validity + +One side-effect of a certificate being self-signed is that its Subject DN and +its Issuer DN are identical. The X.509 [RFC 5280, section 4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4) +requires that: + +> The issuer field MUST contain a non-empty distinguished name (DN). + +However, self-signed certs don't have a subject DN set by default. Unless you +manually set a certificate's Subject DN, the Issuer DN will be empty +and the certificate will technically be invalid. + +Validation of this specific area of the spec is patchy and varies between TLS +libraries, but there's always the risk that a library will improve its +validation - entirely within spec - in the future and break your app if you're +using a certificate with an empty Issuer DN. + +To avoid this, be sure to set a Subject for `SelfSigned` certs. This can be +done by setting the `spec.subject` on a cert-manager `Certificate` object +which will be issued by a `SelfSigned` issuer. + +Starting in version 1.3, cert-manager will emit a Kubernetes [warning event](https://github.com/cert-manager/cert-manager/blob/45befd86966c563663d18848943a1066d9681bf8/pkg/controller/certificaterequests/selfsigned/selfsigned.go#L140) +of type `BadConfig` if it detects that a certificate is being created +by a `SelfSigned` issuer which has an empty Issuer DN. diff --git a/content/v1.13-docs/configuration/vault.md b/content/v1.13-docs/configuration/vault.md new file mode 100644 index 0000000000..f2b15b5435 --- /dev/null +++ b/content/v1.13-docs/configuration/vault.md @@ -0,0 +1,387 @@ +--- +title: Vault +description: 'cert-manager configuration: Vault Issuers' +--- + +The `Vault` `Issuer` represents the certificate authority +[Vault](https://www.vaultproject.io/) - a multi-purpose secret store that can be +used to sign certificates for your Public Key Infrastructure (PKI). Vault is an +external project to cert-manager and as such, this guide will assume it has been +configured and deployed correctly, ready for signing. You can read more on how +to configure Vault as a certificate authority +[here](https://www.vaultproject.io/docs/secrets/pki/). + +This `Issuer` type is typically used when Vault is already being used within +your infrastructure, or you would like to make use of its feature set where the +CA issuer alone cannot provide. + +## Deployment + +All Vault issuers share common configuration for requesting certificates, +namely the server, path, and CA bundle: + +- Server is the URL whereby Vault is reachable. +- Path is the Vault path that will be used for signing. Note that the path + *must* use the `sign` endpoint. +- CA bundle denotes an optional field containing a base64 encoded string of the + Certificate Authority to trust the Vault connection. This is typically + _always_ required when using an `https` URL. + +Below is an example of a configuration to connect a Vault server. + +> **Warning**: This configuration is incomplete as no authentication methods have +> been added. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: vault-issuer + namespace: sandbox +spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + ... +``` + +## Authenticating + +In order to request signing of certificates by Vault, the issuer must be able to +properly authenticate against it. cert-manager provides multiple approaches to +authenticating to Vault which are detailed below. + +### Authenticating via an AppRole + +An [AppRole](https://www.vaultproject.io/docs/auth/approle.html) is a method of +authenticating to Vault through use of its internal role policy system. This +authentication method requires that the issuer has possession of the `SecretID` +secret key, the `RoleID` of the role to assume, and the app role path. Firstly, +the secret ID key must be stored within a Kubernetes `Secret` that resides in the +same namespace as the `Issuer`, or otherwise inside the `Cluster Resource +Namespace` in the case of a `ClusterIssuer`. + +```yaml +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: cert-manager-vault-approle + namespace: sandbox +data: + secretId: "MDI..." +``` + +Once the `Secret` has been created, the `Issuer` is ready to be deployed which +references this `Secret`, as well as the data key of the field that stores the +secret ID. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: vault-issuer + namespace: sandbox +spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + appRole: + path: approle + roleId: "291b9d21-8ff5-..." + secretRef: + name: cert-manager-vault-approle + key: secretId +``` + +### Authenticating with a Token + +This method of authentication uses a token string that has been generated from +one of the many authentication backends that Vault supports. These tokens have +an expiry and so need to be periodically refreshed. You can read more on Vault +tokens [here](https://www.vaultproject.io/docs/concepts/tokens.html). + +> **Note**: cert-manager does not refresh these token automatically and so another +> process must be put in place to do this. + +Firstly, the token is be stored inside a Kubernetes `Secret` inside the same +namespace as the `Issuer` or otherwise in the `Cluster Resource Namespace` in +the case of using a `ClusterIssuer`. + +```yaml +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: cert-manager-vault-token + namespace: sandbox +data: + token: "MjI..." +``` + +Once submitted, the Vault issuer is able to be created using token +authentication by referencing this `Secret` along with the key of the field the +token data is stored at. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: vault-issuer + namespace: sandbox +spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + tokenSecretRef: + name: cert-manager-vault-token + key: token +``` + + + +### Authenticating with Kubernetes Service Accounts + +The [Vault Kubernetes +Auth](https://developer.hashicorp.com/vault/docs/auth/kubernetes) allows +cert-manager to authenticate to Vault using a Kubernetes Service Account Token +in order to issue certificates using Vault as a certification authority. The +Kubernetes service account token can be provided in two ways: + +- [Secretless Authentication with a Service Account](#secretless-authentication-with-a-service-account) (recommended), +- [Authentication with a Static Service Account Token](#static-service-account-token). + +#### Secretless Authentication with a Service Account + +ℹ️ This feature is available in cert-manager >= v1.12.0. + +With the secretless authentication with a service account, cert-manager creates +an ephemeral service account token using the TokenRequest API and uses it to +authenticate with Vault. These tokens are short-lived (10 minutes) and are +never stored to disk. + +This is the recommended authentication method because it does not rely on the +deprecated static service account tokens. The static service account tokens pose +a threat due to their infinite lifetime. Static service account tokens have been +disabled by default on Kubernetes 1.24. + +The first step is to create a `ServiceAccount` resource: + +```sh +kubectl create serviceaccount -n sandbox vault-issuer +``` + +Then add an RBAC Role so that cert-manager can get tokens for the +ServiceAccount: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: vault-issuer + namespace: sandbox +rules: + - apiGroups: [''] + resources: ['serviceaccounts/token'] + resourceNames: ['vault-issuer'] + verbs: ['create'] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: vault-issuer + namespace: sandbox +subjects: + - kind: ServiceAccount + name: cert-manager + namespace: cert-manager +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: vault-issuer +``` + +Finally, create the Issuer resource: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: vault-issuer + namespace: sandbox +spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + auth: + kubernetes: + role: my-app-1 + mountPath: /v1/auth/kubernetes + serviceAccountRef: + name: vault-issuer +``` + +> **Issuer vs. ClusterIssuer:** With an Issuer resource, you can only refer to a +> service account located in the same namespace as the Issuer. With a +> ClusterIssuer, the service account must be located in the namespace that is +> configured by the flag `--cluster-resource-namespace`. + +To create the role in Vault, you can use the following command: + +```bash +vault write auth/kubernetes/role/vault-issuer \ + bound_service_account_names=vault-issuer \ + bound_service_account_namespaces=sandbox \ + audience="vault://sandbox/vault-issuer" \ + policies=vault-issuer \ + ttl=1m +``` + +It is recommended to use a different Vault role each per Issuer or +ClusterIssuer. The `audience` allows you to restrict the Vault role to a single +Issuer or ClusterIssuer. The syntax is the following: + +```yaml +"vault:///" # For an Issuer. +"vault://" # For a ClusterIssuer. +``` + +The expiration duration for the Kubernetes tokens that are requested is +hard-coded to 10 minutes (that's the minimum accepted). The `ttl` field can be +as short as possible, since cert-manager requests a new token every time it +needs to talks to Vault. + +Although it is not recommended, you can also use the same Vault role for all of +your Issuers and ClusterIssuers by omitting the `audience` field and re-using +the same service account. + + +#### Authentication with a Static Service Account Token + +For the Vault issuer to use this authentication, cert-manager must get access to +the token that is stored in a Kubernetes `Secret`. Kubernetes Service Account +Tokens are already stored in `Secret` resources however, you must ensure that +it is present in the same namespace as the `Issuer`, or otherwise in the +`Cluster Resource Namespace` in the case of using a `ClusterIssuer`. + +> **Note**: In Kubernetes 1.24 onwards, the token secret is no longer created +> by default for the Service Account. In this case you need to manually create +> the secret resource. See [this guide](https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets) +> for more details. + +This authentication method also expects a `role` field which is the Vault role +that the Service Account is to assume, as well as an optional `mountPath` field which +is the authentication mount path, defaulting to `kubernetes`. + +#### Kubernetes version less than 1.24 + +The following example will be making use of the Service Account +`my-service-account`. The secret data field key will be `token` if the `Secret` +has been created by Kubernetes. The Vault role used is `my-app-1`, using the +default mount path of `/v1/auth/kubernetes` + +1) Create the Service Account: + + ```shell + kubectl create serviceaccount -n sandbox vault-issuer + ``` + +1) Get the auto-generated Secret name: + + ```shell + kubectl get secret -o json | jq -r '.items[] | select(.metadata.annotations["kubernetes.io/service-account.name"] == "vault-issuer") | .metadata.name' + ``` + +1) Create the Issuer using that Secret name retrieved from the previous step: + + ```yaml + apiVersion: cert-manager.io/v1 + kind: Issuer + metadata: + name: vault-issuer + namespace: sandbox + spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + kubernetes: + role: my-app-1 + mountPath: /v1/auth/kubernetes + secretRef: + name: + key: token + ``` + +#### Kubernetes version 1.24 and greater + +This example is almost the same as above but adjusted for the change in +Kubernetes 1.24 and above. + +1) Create the Service Account: + + ```shell + kubectl create serviceaccount -n sandbox vault-issuer + ``` + +1) Create the Secret resource for Kubernetes to populate the `token` value: + + ```yaml + apiVersion: v1 + kind: Secret + metadata: + name: vault-issuer-token + annotations: + kubernetes.io/service-account.name: "vault-issuer" + type: kubernetes.io/service-account-token + data: {} + ``` + +1) Create the Issuer resource referencing the Secret resource: + + ```yaml + apiVersion: cert-manager.io/v1 + kind: Issuer + metadata: + name: vault-issuer + namespace: sandbox + spec: + vault: + path: pki_int/sign/example-dot-com + server: https://vault.local + caBundle: + auth: + kubernetes: + role: my-app-1 + mountPath: /v1/auth/kubernetes + secretRef: + name: vault-issuer-token + key: token + ``` + +## Verifying the issuer Deployment + +Once the Vault issuer has been deployed, it will be marked as ready if the +configuration is valid. Replace `issuers` below with `clusterissuers` if that is what has +been deployed. + +The Vault issuer tests your Vault instance by querying the `v1/sys/health` +endpoint, to ensure your Vault instance is unsealed and initialized before +requesting certificates. The result of that query will populate the `STATUS` +column. + +```bash +$ kubectl get issuers vault-issuer -n sandbox -o wide +NAME READY STATUS AGE +vault-issuer True Vault verified 2m +``` + +Certificates are now ready to be requested by using the Vault issuer named +`vault-issuer` within the `sandbox` namespace. diff --git a/content/v1.13-docs/configuration/venafi.md b/content/v1.13-docs/configuration/venafi.md new file mode 100644 index 0000000000..29cbbd04c7 --- /dev/null +++ b/content/v1.13-docs/configuration/venafi.md @@ -0,0 +1,282 @@ +--- +title: Venafi +description: 'cert-manager configuration: Venafi Issuers' +--- + +## Introduction + +The Venafi `Issuer` types allows you to obtain certificates from [Venafi +as a Service (VaaS)](https://vaas.venafi.com/jetstack) and [Venafi Trust Protection +Platform (TPP)](https://www.venafi.com/platform/tls-protect) instances. + +You can have multiple different Venafi `Issuer` types installed within the same +cluster, including mixtures of Venafi as a Service and TPP issuer types. This allows +you to be flexible with the types of Venafi account you use. + +Automated certificate renewal and management are provided for `Certificates` +using the Venafi `Issuer`. + +A single Venafi `Issuer` represents a single Venafi 'zone' so you must create one +`Issuer` resource for each zone you want to use. A zone is a single entity that +combines the policy that governs certificate issuance with information about how +certificates are organized in Venafi to identify the business application and +establish ownership. + +You can configure your `Issuer` resource to either issue certificates only +within a single namespace, or cluster-wide (using a `ClusterIssuer` resource). +For more information on the distinction between `Issuer` and `ClusterIssuer` +resources, read the [Namespaces](../concepts/issuer.md#namespaces) section. + +## Creating a Venafi as a Service Issuer + +If you haven't already done so, create your Venafi as a Service account on this +[page](https://vaas.venafi.com/jetstack) and copy the API key from your user +preferences. Then you may want to create a custom CA Account and Issuing Template +or choose instead to use defaults that are automatically created for testing +("Built-in CA" and "Default", respectively). Lastly you'll need to create an +Application for establishing ownership of all the certificates requested by your +cert-manager Issuer, and assign to it the Issuing Template. + +> Make a note of the Application name and API alias of the Issuing Template because +> together they comprise the 'zone' you will need for your `Issuer` configuration. + +In order to set up a Venafi as a Service `Issuer`, you must first create a Kubernetes +`Secret` resource containing your Venafi as a Service API credentials: + +```bash +$ kubectl create secret generic \ + vaas-secret \ + --namespace='NAMESPACE OF YOUR ISSUER RESOURCE' \ + --from-literal=apikey='YOUR_VAAS_API_KEY_HERE' +``` + +> **Note**: If you are configuring your issuer as a `ClusterIssuer` resource in +> order to serve `Certificates` across your whole cluster, you must set the +> `--namespace` parameter to `cert-manager`, which is the default `Cluster +> Resource Namespace`. The `Cluster Resource Namespace` can be configured +> through the `--cluster-resource-namespace` flag on the cert-manager controller +> component. + +This API key will be used by cert-manager to interact with Venafi as a Service +on your behalf. + +Once the API key `Secret` has been created, you can create your `Issuer` or +`ClusterIssuer` resource. If you are creating a `ClusterIssuer` resource, you +must change the `kind` field to `ClusterIssuer` and remove the +`metadata.namespace` field. + +Save the below content after making your amendments to a file named +`vaas-issuer.yaml`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: vaas-issuer + namespace: +spec: + venafi: + zone: "My Application\My CIT" # Set this to \ + cloud: + apiTokenSecretRef: + name: vaas-secret + key: apikey +``` + +You can then create the Issuer using `kubectl create`. + +```bash +$ kubectl create -f vaas-issuer.yaml +``` + +Verify the `Issuer` has been initialized correctly using `kubectl describe`. + +```bash +$ kubectl get issuer vaas-issuer --namespace='NAMESPACE OF YOUR ISSUER RESOURCE' -o wide +NAME READY STATUS AGE +vaas-issuer True Venafi issuer started 2m +``` + +You are now ready to issue certificates using the newly provisioned Venafi +`Issuer` and Venafi as a Service. + +Read the [Requesting Certificates](../usage/certificate.md) document for +more information on how to create Certificate resources. + + +## Creating a Venafi Trust Protection Platform Issuer + +The Venafi Trust Protection Platform integration allows you to obtain certificates +from a properly configured Venafi TPP instance. + +The setup is similar to the Venafi as a Service configuration above, however some +of the connection parameters are slightly different. + +> **Note**: You *must* allow "User Provided CSRs" as part of your TPP policy, as +> this is the only type supported by cert-manager at this time. +> +> More specifically, the valid configurations of the "CSR handling" are: +> +> - "User Provided CSRs" selected and unlocked, +> - "User Provided CSRs" selected and locked, +> - "Service Generated CSRs" selected and unlocked. +> +> When using "Service Generated CSRs" selected and unlocked, the default CSR +> configuration present in your policy folder will override the configuration of +> your Certificate resource. The subject DN, key algorithm, and key size will be +> overridden by the values set in the policy folder. +> +> With "Service Generated CSRs" selected and locked, the certificate issuance +> will systematically fail with the following message: +> +> ```plain +> 400 PKCS#10 data will not be processed. Policy "\VED\Policy\foo" is locked to a Server Generated CSR. +> ``` + +In order to set up a Venafi Trust Protection Platform `Issuer`, you must first +create a Kubernetes `Secret` resource containing your Venafi TPP API +credentials. + +### Access Token Authentication + +1. [Set up token authentication](https://docs.venafi.com/Docs/23.1/TopNav/Content/SDK/AuthSDK/t-SDKa-Setup-OAuth.php). + + NOTE: Do not select "Refresh Token Enabled" and set a *long* "Token Validity (days)". + +2. Create a new user with sufficient privileges to manage and revoke certificates in a particular policy folder (zone). + + E.g. `k8s-xyz-automation` + +3. [Create a new application integration](https://docs.venafi.com/Docs/21.4/TopNav/Content/API-ApplicationIntegration/t-APIAppIntegrations-creatingNew-Aperture.php) + + Create an application integration with name and ID `cert-manager`. + Set the "API Access Settings" to `Certificates: Read,Manage,Revoke`. + + "Edit Access" to the new application integration, and allow it to be used by the user you created earlier. + +4. [Generate an access token](https://github.com/Venafi/vcert/blob/master/README-CLI-PLATFORM.md#obtaining-an-authorization-token) + + ``` + vcert getcred \ + --username k8s-xyz-automation \ + --password somepassword \ + -u https://tpp.example.com/vedsdk \ + --client-id cert-manager \ + --scope "certificate:manage,revoke" + ``` + + This will print an access-token to `stdout`. E.g. + + ``` + vCert: 2020/10/07 16:34:27 Getting credentials + access_token: I69n.............y1VjNJT3o9U0Wko19g== + access_token_expires: 2021-01-05T15:34:30Z + ``` + +5. Save the access-token to a Secret in the Kubernetes cluster + +```bash +$ kubectl create secret generic \ + tpp-secret \ + --namespace= \ + --from-literal=access-token='YOUR_TPP_ACCESS_TOKEN' +``` + +### Username / Password Authentication + +> ⚠️ When you supply a Venafi TPP username and password, +> cert-manager uses an older authentication method which is called "API Keys", +> which has been deprecated since Venafi TPP `19.2`. +> +> Beginning in Venafi TPP `22.2`, "API Keys" are disabled by default. +> You will need to contact Venafi customer support for a special license key which will allow you to re-enable the "API Keys" feature, +> so that you can continue to use username and password authentication with cert-manager. +> +> In Venafi TPP `22.3`, the "API Keys" feature will be permanently removed, +> and you will need to use access-token authentication instead. +> +> 📖 Read [Deprecated functionality from Venafi Platform](https://docs.venafi.com/22.3/deprecation-list-current) +> and [Functionality Scheduled for Deprecation](https://support.venafi.com/hc/en-us/articles/115001662292) for more information. + +```bash +$ kubectl create secret generic \ + tpp-secret \ + --namespace= \ + --from-literal=username='YOUR_TPP_USERNAME_HERE' \ + --from-literal=password='YOUR_TPP_PASSWORD_HERE' +``` + +> Note: If you are configuring your issuer as a `ClusterIssuer` resource in +> order to issue `Certificates` across your whole cluster, you must set the +> `--namespace` parameter to `cert-manager`, which is the default `Cluster +> Resource Namespace`. The `Cluster Resource Namespace` can be configured +> through the `--cluster-resource-namespace` flag on the cert-manager controller +> component. + +These credentials will be used by cert-manager to interact with your Venafi TPP +instance. Username attribute must be adhere to the `:` format. For example: `local:admin`. + +Once the Secret containing credentials has been created, you can create your +`Issuer` or `ClusterIssuer` resource. If you are creating a `ClusterIssuer` +resource, you must change the `kind` field to `ClusterIssuer` and remove the +`metadata.namespace` field. + +Save the below content after making your amendments to a file named +`tpp-issuer.yaml`. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: tpp-issuer + namespace: +spec: + venafi: + zone: \VED\Policy\devops\cert-manager # Set this to the Venafi policy folder you want to use + tpp: + url: https://tpp.venafi.example/vedsdk # Change this to the URL of your TPP instance + caBundle: + credentialsRef: + name: tpp-secret +``` + +You can then create the `Issuer` using `kubectl create -f`. + +```bash +$ kubectl create -f tpp-issuer.yaml +``` + +Verify the `Issuer` has been initialized correctly using `kubectl describe`. + +```bash +$ kubectl describe issuer tpp-issuer --namespace='NAMESPACE OF YOUR ISSUER RESOURCE' +``` + +You are now ready to issue certificates using the newly provisioned Venafi +`Issuer` and Trust Protection Platform. + +Read the [Requesting Certificates](../usage/certificate.md) document for +more information on how to create Certificate resources. + +## Issuer specific annotations + +### Custom Fields + +Starting `v0.14` you can pass custom fields to Venafi (TPP version `v19.2` and higher) using the `venafi.cert-manager.io/custom-fields` annotation on Certificate resources. +The value is a JSON encoded array of custom field objects having a `name` and `value` key. +For example: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-certificate + annotations: + venafi.cert-manager.io/custom-fields: |- + [ + {"name": "field-name", "value": "field value"}, + {"name": "field-name-2", "value": "field value 2"} + ] +... +``` diff --git a/content/v1.13-docs/contributing/README.md b/content/v1.13-docs/contributing/README.md new file mode 100644 index 0000000000..aec6b21254 --- /dev/null +++ b/content/v1.13-docs/contributing/README.md @@ -0,0 +1,68 @@ +--- +title: Contributing +description: 'cert-manager contributing guide: Get involved!' +--- + +## Great to See You! + +Whether you're a previous contributor or a first timer looking to get involved, we love +it when the community comes together to improve the project! + +In this "contributing" section we document processes we follow as a project, and include +some details on how to build, test and run cert-manager for development purposes. + +## Meetings + +All cert-manager meetings are open for everyone to join; if you have a question or a suggestion or just want to chat, +please feel free to come along and get involved! + +To get invites you can subscribe to [our mailing list](https://groups.google.com/forum/#!forum/cert-manager-dev) and +you should receive calendar invites by mail shortly after joining. The complexities of calendars mean that some invites +might be sent multiple times depending on your email and calendar providers and you might get some invites for past +or future meetings which have been rescheduled or edited. Sorry about that! + +We have 2 regular repeating meetings: our quick daily check-in and an hour-long community meeting every two weeks. + +If you're having any issues joining our meetings, ensure that you're part of the [`cert-manager-dev`](https://groups.google.com/forum/#!forum/cert-manager-dev) Google group, and always feel free to ask in [Slack](./#slack) for help! + +
        +🔰 All of our meetings happen on London (UK) time; you can add London to the world clocks on your phone to avoid confusion! + +When daylight savings time changes in London might be different to when it changes for you if you live in a place that either +doesn't have DST or which changes on a different schedule like North America or Australia! +
        + +### Daily Check-In + +Our daily check-in meetings [happen on Google Meet](https://meet.google.com/eum-fyvt-xpa) at [10:30 London time](http://www.thetimezoneconverter.com/?t=10:30&tz=Europe/London) every weekday. + +The format is a 5 minute social chat, followed by a quick round-robin status report and ending with any longer form talking points. + +The status report is a stand-up where we talk about work done yesterday, work coming up and highlight any blockers. +We'll try to keep to a **strict time limit** during these status reports of around 1 minute per person. + +Please don't be offended if someone steps in when you run out of time and moves the reports along to the next person - the idea +is for everyone to be succinct so it's clear what's being worked on and who is blocked. + +We finish with talking points, which are open-ended discussions on any topic related to cert-manager or its sub-projects. +We'll ensure that anyone outside of the core maintainer team who has a talking point goes first. + +### Community Meetings + +Our bi-weekly (i.e. every two weeks) community meetings happen [on Google Meet](https://meet.google.com/iga-jwvx-nye) at [17:00 London time](http://www.thetimezoneconverter.com/?t=17:00&tz=Europe/London) (for dates, check calendar invites or [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U)). + +These meetings are an hour-long chat about cert-manager topics. It's a great way to get involved with contributing for the +first time; to get answers to any questions you might have; or to propose a new feature which needs some explanation. + +If you want to discuss something, please add it to the [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U) +before the meeting. The meeting chair will try to get to everything that was on the notes before the meeting started. + +We try to record these meetings and put them on YouTube so they can be checked later - if you don't want to appear on video please keep +your camera off! + +## Slack + +We have two cert-manager channels on [Kubernetes Slack](https://slack.k8s.io) which we use to chat: + +* [`cert-manager`](https://kubernetes.slack.com/messages/cert-manager): for all users of cert-manager; use this one for any usage related questions +* [`cert-manager-dev`](https://kubernetes.slack.com/messages/cert-manager-dev): for collaboration between cert-manager contributors and maintainers; please only use this for code related questions diff --git a/content/v1.13-docs/contributing/building.md b/content/v1.13-docs/contributing/building.md new file mode 100644 index 0000000000..92fd7a996f --- /dev/null +++ b/content/v1.13-docs/contributing/building.md @@ -0,0 +1,267 @@ +--- +title: Building cert-manager +description: 'cert-manager contributing guide: Building cert-manager' +--- + +cert-manager is built and tested using [make](https://www.gnu.org/software/make/manual/make.html), with a focus on using the standard Go tooling +where possible and keeping system dependencies to a minimum. The cert-manager build system can provision most of its dependencies - including Go - +automatically if required. + +cert-manager's build system fully supports developers who use `Linux amd64`, `macOS amd64` and `macOS arm64`. + +It also has limited support for `Linux arm64`, although that platform is largely untested and isn't fully supported. + +Other operating systems and architectures may work but will likely require hacks and workarounds to develop on. + +## Prerequisites + +There are very few other requirements needed for developing cert-manager, and crucially the build system should tell you with a friendly error +message if there's anything missing. If you think an error message which relates to a missing dependency is unhelpful, we consider that a bug and +we'd appreciate if you raised [an issue](https://github.com/cert-manager/cert-manager/issues/new?assignees=&labels=&template=bug.md) to tell us about it! + +You should install the following tools before you start developing cert-manager: + +- [git](https://git-scm.com/) +- [curl](https://curl.se/) +- [GNU make](https://www.gnu.org/software/make/manual/make.html), `v3.82` or newer +- GNU Coreutils (usually already installed on Linux, available via [homebrew](https://formulae.brew.sh/formula/coreutils) for macOS) +- `jq` (available in Linux package managers and in [homebrew](https://formulae.brew.sh/formula/jq)) +- `docker` (or `podman`, see [Container Engines](#container-engines) below) +- `Go` (optional; see [Go Versions](#go-versions) below) + +## Getting Started + +The vast majority of commands which you're likely to need to use are documented via `make help`. That's probably the first place to start if you're +developing cert-manager. We'll also provide an overview on this page of some of the key targets and things to bear in mind. + +### Go Versions + +cert-manager defaults to using whatever version of Go you've installed locally on your system. If you want to use your system Go, that's totally fine. + +Alternatively, make can provision and "vendor" Go specifically for cert-manager, helping to ensure you use the same version that's used in CI and to +make it easier to get started developing. + +To start using a vendored Go, run: `make vendor-go`. + +You only need to run `vendor-go` once and it'll be "sticky", being used for all future make invocations in your local checkout. + +To return to using your system version of go, run: `make unvendor-go`. + +To check which version of Go is _currently_ being used, run: `make which-go`, which prints the version number of Go and the path to the Go binary. + +```console +# Use a vendored version of go +$ make vendor-go +cd _bin/tools/ && ln -f -s ../downloaded/tools/_go-1.XY.Z-linux-amd64/goroot . +cd _bin/tools/ && ln -f -s ../downloaded/tools/_go-1.XY.Z-linux-amd64/goroot/bin/go . + +# A path to go inside the cert-manager directory indicates that a vendored Go is being used +$ make which-go +go version go1.XY.Z linux/amd64 +go binary used for above version information: /home/user/workspace/cert-manager/_bin/tools/go + +# Go back to the system Go +$ make unvendor-go +rm -rf _bin/tools/go _bin/tools/goroot + +# The binary is now "go" which should be found in $PATH +$ make which-go +go version go1.AB.C linux/amd64 +go binary used for above version information: go +``` + +### Go Workspaces + +In short: Some development tools will complain about cert-manager's module layout; to help with this, generate a +`go.work` file using `make go-workspace`. + +The cert-manager repository as of cert-manager 1.12 contains multiple Go modules, in a setup where only the core module `github.com/cert-manager/cert-manager` +is expected to be imported by third party modules. There are separate modules (which we call submodules), all of which have replace statements for the core +cert-manager module. + +This setup is intentional to convey that these submodules are not intended to be imported by third parties, and to ensure that each submodule always uses +whatever the cert-manager core module version is at the same commit - but this structure can have the side effect that certain development tools and scripts +will not work as expected. + +As an example, `go test ./...` will by default only affect the core module. To test, say, the controller, you'd need to use `cd cmd/controller && go test ./...`. + +This can be avoided through the use of go workspaces, which will handle local replacements for you and work better with editors such as VS Code. + +You can run `make go-workspace` to generate a `go.work` file which should enable `go test ./...` to work across the +whole repo, and which should help editors to understand the module structure. + +Note that go workspaces are not used when testing pull requests in CI. If you see errors in CI which you can't replicate +locally, try building with the `GOWORK` environment variable set to `off` or deleting the `go.work` file. + +### Parallelism + +The cert-manager Makefile is designed to be highly parallel wherever possible. Any build and test commands should be able to be executed in parallel using +standard Make functionality. + +One important caveat is that that Go will default to detecting the number of cores available on the system and spinning up as many threads as it can. If you're +using Make functionality to run multiple builds in parallel, this number of threads can be excessive and actually lead to slower builds. + +It's possible to limit the number of threads Go uses we'd generally recommend doing so when using Make parallelism. + +The best values to use will depend on your system, but we've had success using around half of the available number of cores for Make and limiting Go to between +2 and 4 threads per core. + +For example, using an 8-core machine: + +```bash +# Run 4 make targets in parallel, and limit each `go build` to 2 threads. +make GOMAXPROCS=2 -j4 release-artifacts +``` + +## Testing + +cert-manager's build pipeline and CI infrastructure uses the same Makefile that you use when developing locally, +so there should be no divergence between what the tests run and what you run. That means you should be able to be pretty confident that any changes you make +won't break when tested in CI. + +### Running Local Changes in a Cluster + +It's common that you might want to run a local Kubernetes cluster with your locally-changed copy of cert-manager in it, for manual testing. + +There are make targets to help with this; see [Developing with Kind](./kind.md) for more information. + +### Unit and Integration Tests + +First of all: If you want to test using `go test`, feel free! For unit tests (which we define as any test outside of the `test/` directory), `go test` will +work on a fresh checkout. + +Note that the cert-manager repo is split into multiple modules and unless you're using go workspaces `go test ./...` won't actually run all tests. See [Go Workspaces](./building.md#go-workspaces) above for more details. + +Integration tests may require some external tools to be set up first, so to run the integration tests inside `test/` you might need to run: + +```bash +make setup-integration-tests +``` + +Helper targets are also available which use [`gotestsum`](https://github.com/gotestyourself/gotestsum) for prettier output. It's also possible to +configure these targets to run specific tests: + +```bash +# Run all unit and integration tests +make test + +# Run only unit tests +make unit-test + +# Run only integration tests +make integration-test + +# Run all tests in pkg +make WHAT=./pkg/... test + +# Run unit and integration tests exactly as run in CI +# (NB: usually not needed - this is mostly for JUnit test output for dashboards) +make test-ci +``` + +### End-to-End Testing + +cert-manager's end-to-end tests are a little more involved and have [dedicated documentation](./e2e.md) describing their use. + +### Other Checks + +We run a variety of other tools on every Pull Request to check things like formatting, import ordering and licensing. These checks can all be run locally: + +```bash +make ci-presubmit +``` + +NB: One of these checks currently requires Python 3 to be installed, which is a unique requirement in the code base. We'd like to remove that requirement in the future. + +## Updating CRDs and Code Generation + +Changes to cert-manager's CRDs require some code generation to be done, which will be checked on every pull request. + +If you make changes to cert-manager CRDs, you'll need to run some commands locally before raising your PR. + +This is documented in our [CRDs](./crds.md) section. + +## Building + +cert-manager produces many artifacts for a lot of different OS / architecture combinations, including: + +- Container images +- Client binaries (`cmctl` and `kubectl_cert-manager`) +- Manifests (Helm charts, static YAML) + +All of these artifacts can be built locally using make. + +### Containers + +cert-manager's most important artifacts are the containers which actually run cert-manager in a cluster. We default to using `docker` for this, +but aim to support docker-compatible CLI tools such as `podman`, too. See [Container Engines](#container-engines) for more info. + +There are several targets for building different cert-manager containers locally. These will all default to using `docker`: + +```bash +# Build everything for every architecture +make all-containers + +# Build just the controller containers on every architecture +make cert-manager-controller-linux + +# As above, but for the webhook, cainjector, acmesolver and cmctl containers +make cert-manager-webhook-linux +make cert-manager-cainjector-linux +make cert-manager-acmesolver-linux +make cert-manager-ctl-linux +``` + +#### Container Engines + +NB: This section doesn't apply to end-to-end tests, which might not work outside of Docker at the time of writing. See the [end-to-end documentation](./e2e.md#container-engines) +for more information. + +It's possible to use an alternative container engine to build cert-manager containers. This has been successfully tested using `podman`. + +Configure an alternative container engine by setting the `CTR` variable: + +```bash +# Build everything for every architecture, using podman +make CTR=podman all-containers +``` + +### Client Binaries + +Both `cmctl` and `kubectl_cert-manager` can be built locally for a release. These binaries are built for Linux, macOS and Windows across several architectures. + +```bash +# Build all cmctl binaries for all platforms, then for linux only, then for macOS only, then for Windows only +make cmctl +make cmctl-linux +make cmctl-darwin +make cmctl-windows + +# As above but for kubectl_cert-manager +make kubectl_cert-manager +make kubectl_cert-manager-linux +make kubectl_cert-manager-darwin +make kubectl_cert-manager-windows +``` + +### Manifests + +We use "manifests" as a catch-all term for non-binary artifacts which we build as part of a release including static installation YAML and our Helm chart. + +Everything can be built using make: + +```bash +make helm-chart +make static-manifests +``` + +### Everything + +Sometimes it's useful to build absolutely everything locally, to be sure that a change didn't break some obscure architecture and to build confidence when raising a PR. + +It's not easy to build a _complete_ release locally since a full release includes signatures which depend on KMS keys being configured. Most users probably don't +need that, though, and for this use case there's a make target which will build everything except the signed artifacts: + +```bash +make GOMAXPROCS=2 -j4 release-artifacts +``` diff --git a/content/v1.13-docs/contributing/coding-conventions.md b/content/v1.13-docs/contributing/coding-conventions.md new file mode 100644 index 0000000000..8001cc387d --- /dev/null +++ b/content/v1.13-docs/contributing/coding-conventions.md @@ -0,0 +1,59 @@ +--- +title: Coding Conventions +description: 'cert-manager contributing guide: Coding conventions' +--- + +cert-manager, like most Go projects, delegates almost all stylistic choices to `gofmt`, +with `goimports` on top for organizing imports. Broadly speaking, if you set your editor to run +`goimports` when you save a file, your code will be stylistically correct. + +cert-manager generally also follows the Kubernetes +[coding conventions](https://www.kubernetes.dev/docs/guide/coding-convention/) and the Google +[Go code review comments](https://github.com/golang/go/wiki/CodeReviewComments). + +## Organizing Imports + +Imports should be organized into 3 blocks, with each block separated by two newlines: + +```go +import ( + "stdlib" + + "external" + + "internal" +) +``` + +An example might be the following, taken from +[`pkg/acme/accounts/client.go`](https://github.com/cert-manager/cert-manager/blob/0c71fe7795858b96cabcddabf706d997cd2fba3f/pkg/acme/accounts/client.go): + +```go +import ( + "crypto/rsa" + "crypto/tls" + "net" + "net/http" + "time" + + acmeapi "golang.org/x/crypto/acme" + + acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" + acmeutil "github.com/cert-manager/cert-manager/pkg/acme/util" + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + "github.com/cert-manager/cert-manager/pkg/metrics" + "github.com/cert-manager/cert-manager/pkg/util" +) +``` + +Once this manual split of standard library, external and internal imports has been made, it will be +enforced automatically by `goimports` when executed in the future. + +## UK vs. US spelling + +For the sake of consistency, cert-manager uses en-US spelling for the +documentation in https://cert-manager.io as well as within the cert-manager +codebase. A comprehensive list of en-GB → en-US word substitution is available +on Ubuntu's +[`WordSubstitution`](https://wiki.ubuntu.com/EnglishTranslation/WordSubstitution) +page. \ No newline at end of file diff --git a/content/v1.13-docs/contributing/contributing-flow.md b/content/v1.13-docs/contributing/contributing-flow.md new file mode 100644 index 0000000000..bd5dff0669 --- /dev/null +++ b/content/v1.13-docs/contributing/contributing-flow.md @@ -0,0 +1,178 @@ +--- +title: Contributing Flow +description: 'cert-manager contributing guide: Contribution flow' +--- + +All of cert-manager's development is done via +[GitHub](https://github.com/cert-manager/cert-manager) which contains code, issues and pull +requests. + +All code for the documentation and cert-manager.io can be found at [the cert-manager/website repo](https://github.com/cert-manager/website/). +Any issues towards the documentation should also be filed there. + +## GitHub bot + +We use [Prow](https://github.com/k8s-ci-robot/test-infra/tree/master/prow) on all our repositories. +If you've ever looked at a Kubernetes repo, you will probably already have met Prow. Prow will be able to help you in GitHub using its commands. +You can find then all [on the command help page](https://prow.build-infra.jetstack.net/command-help). +Prow will also run all tests and assign certain labels on PRs. + +## Bugs + +All bugs should be tracked as issues inside the +[GitHub](https://github.com/cert-manager/cert-manager/issues) repository. Issues should then be +attached with the `kind/bug` tag. To do this add `/kind bug` to your issue description. +This may then be assigned a priority and milestone to be addressed in a future release. + +The more logs and information you can give about what and how the bug has been +discovered, the faster it can be resolved. + +Critical bug fixes are typically also cherry picked to the current minor stable releases. + +> Note: If you are simply looking for _troubleshooting_ then you should post +> your question to the community `cert-manager` [slack channel](https://slack.k8s.io). +> Many more people read this channel than GitHub issues, it's likely your problem will +> be solved quicker by using Slack. +> Please also check that the bug has not already been filed by searching for key +> terms in the issue search bar. + +### (Re)opening and closing issues + +Prow can assist you to reopen or close issues you file, you can trigger it using `/reopen` or `/close` in a GitHub Issue comment. + +## Features + +Feature requests should be created as +[GitHub](https://github.com/cert-manager/cert-manager/issues) issues. They should contain +clear motivation for the feature you wish to see as well as some possible +solutions for how it can be implemented. +Issues should then be tagged with `kind/feature`. To do this add `/kind feature` to your issue description. + +> Note: It is often a good idea to bring your feature request up on the +> community `cert-manager` [slack channel](https://slack.k8s.io) to discuss whether +> the feature request has already been made or is aligned with the project's +> priorities. + +## Creating Pull Requests + +Changes to the cert-manager code base is done via [pull +requests](https://github.com/cert-manager/cert-manager/pulls). Each pull request +should ideally have a corresponding issue attached that is to be fixed by this +pull request. It is valid for multiple pull requests to resolve a single issue +in the interest of keeping code changes self contained and simpler to review. + +Once created, a team member will assign themselves for review and enable +testing. To make sure the changes get merged, keep an eye out for reviews which +can have multiple cycles. + +If the pull request is a critical bug fix then this will probably +also be cherry picked to the current stable version of cert-manager as a patch +release. + +To let people know that your PR is still a work in progress, we usually add a +`WIP:` prefix to the title of the PR. Prow will then automatically set the label +`do-not-merge/work-in-progress`. + +### Release Note Guidelines + +The `release-note` code block in the PR description aims at explaining to the +end-user whether they should care about this change and whether they will be +affected. It is OK to have multiple sentences as long as it is written in good +English (subject verb complement, ends with a dot). + +A release note block shouldn't just paraphrase the commit message. For example, +the end-user doesn't know what "comparisons" are and which custom resources are +affected: + +~~~markdown +```release-note +Adds missing comparisons for certain fields which were incorrectly skipped if a LiteralSubject was set +``` +~~~ + +A better release note block gives context and specifically tells the end-user +how they will be affected: + +~~~markdown +```release-note +When using the `literalSubject` on a Certificate, the IPs, URIs, DNS names, and email addresses subject segments are now properly compared. +``` +~~~ + +New lines in the release note block translate to hard line breaks, which means +it is not possible to soft-wrap the release note. A new line can be useful for +lists: + +~~~markdown +```release-note +cainjector: +- New flags were added to the cainjector binary. They can be used to modify what injectable kinds are enabled. If cainjector is only used as a cert-manager's internal component it is sufficient to only enable validatingwebhookconfigurations and mutatingwebhookconfigurations injectable resources; disabling the rest can improve memory consumption. By default all are enabled. +- The `--watch-certs` flag was renamed to `--enable-certificates-data-source`. +``` +~~~ + +If this PR introduces a breaking change, the release note block must start with +`**BREAKING:**` (note the bold text). + +### Cherry Picking + +If the pull request contains a critical bug fix then this should be cherry picked in to the current stable cert-manager branch +and [released as a patch release](../installation/supported-releases.md#support-policy). + +To trigger the cherry-pick process, add a comment to the GitHub PR. +For example: +``` +/cherry-pick release-x.y +``` + +The `jetstack-bot` will then create a new branch and a PR against the release branch, +which should be reviewed, approved and merged using the process described above. + +### DCO signoff + +All commits in the PR should be signed off, more info on how to do this is at the [DCO Sign Off](./sign-off.md) page. +Exceptions can only be made for small documentation fixes. + +## Project Management + +Most of cert-manager's project management is done on GitHub, with the help of Prow. + +### When will something be released? + +Our team works using [GitHub milestones](https://github.com/cert-manager/cert-manager/milestones). +When a milestone is set on an Issue it is generally an indication of when we plan to address this. +Prow will apply milestones on merged PRs, this will tell you in which version that PR will land. + +The milestone page will also have an indicated due date when we will release. This might have some delay. +We brief our users/contributors about this in our bi-weekly community meeting, for an up to date status report we recommend joining these. + +### Labels + +We make a heavy use of GitHub labels for PRs and Issues. The ones on PRs are mostly managed by Prow and code reviewers. +In issues we always aim to add 3 types: area, priority and kind. These are set using Prow using `/area`, `/kind` and `/priority`. +Sometimes `/triage` is also added which helps us when following up Issues. + +* Area indicates the code area which is/will need changing +* Kind indicates if it is a `bug` or a `feature` but also can be `documentation` or `cleanup` (general maintenance) +* Priority is the priority it has for the cert-manager team, PRs are still very welcome for those! + +### Assignees meaning in PRs and issues + +Sometimes, you might see someone commenting with the +[`/assign` prow command](https://prow.build-infra.jetstack.net/command-help#assign): + +```plain +/assign @meyskens +``` + +Here is the meaning that we give to the GitHub assignees: + +- On issues, it means that the assignee is working on it. +- On PRs, we use it as a way to know who should be taking a look at the PR at any time: + - When the author is assigned, it means the PR needs work to be done aka "changes requested"; + - When nobody is assigned, it means this PR needs review; + - When someone different from the author is assigned, it means this person is reviewing this PR. + +### Triage Party! + +Every few weeks we will plan a Triage Party meeting, where we use the (Triage Party)[https://triage.build-infra.jetstack.net/] tool to go recent/old issues to prioritise them so we can address them in a timely matter. These meetings are open to everyone and invites will be sent out using our mailing list (warning: despite the word party these meetings are sometimes boring). \ No newline at end of file diff --git a/content/v1.13-docs/contributing/crds.md b/content/v1.13-docs/contributing/crds.md new file mode 100644 index 0000000000..1cce203032 --- /dev/null +++ b/content/v1.13-docs/contributing/crds.md @@ -0,0 +1,65 @@ +--- +title: CRDs +description: 'cert-manager contribution guide: CRDs' +--- + +cert-manager uses [Kubernetes Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) to define +the resources which users interact with when using cert-manager, such as `Certificate`s and `Issuer`s. + +When changes are made to the CRDs in code, there are a couple of extra steps which are required. + +## Generating CRD Updates + +We use [`controller-gen`](https://book.kubebuilder.io/reference/controller-gen.html) to update our CRDs, and [`k8s-code-generator`](https://github.com/kubernetes/code-generator) +for code generation. + +Verifying and updating CRDs and generated code can be done entirely through make. There are two steps; one will update CRDs and one will update generated code: + +```bash +# Check that CRDs and codegen are up to date +make verify-crds verify-codegen + +# Update CRDs based on code +make update-crds + +# Update generated code based on CRD defintions in code +make update-codegen +``` + +## Versions + +cert-manager currently has a single `v1` API version for public use. + +cert-manager API types are defined in [`pkg/apis/certmanager`](https://github.com/cert-manager/cert-manager/tree/master/pkg/apis/certmanager). + +ACME related resources are in [`pkg/apis/acme`](https://github.com/cert-manager/cert-manager/tree/master/pkg/apis/acme). + +### Code Comments + +Code comments on API type fields are converted into documentation on this website as well as appearing in the output of `kubectl explain`. + +That means that `go doc`-style comments on API fields should be written to be user-facing and not developer-facing. For this reason it's also fine to break from +usual Go standards regarding code comments when editing these fields. + +### Internal API Versions + +cert-manager also has an internal API version which lives under [`internal/apis`](https://github.com/cert-manager/cert-manager/tree/master/internal/apis). + +The internal version is only used for validation and conversion and controllers should not generally use it; it's not intended to be user-friendly or stable and can change. +However all new fields also have to be added here for the conversion logic to work. + +For details on conversion and versions, see the [official Kubernetes docs for CRD versioning](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/). + +## Kubebuilder + +While cert-manager doesn't fully use Kubebuilder, CRDs can make use of special Kubebuilder flags such as [validation flags](https://book.kubebuilder.io/reference/markers/crd-validation.html). + +## Making Changes to APIs + +Please see our [API compatibility promise](../installation/api-compatibility.md) for details on which types of changes to APIs are acceptable. + +Generally, the gist is that new fields can be added but that existing fields cannot be removed. + +This also means that when a field is added to a version of the API, it's permanent and its name cannot be changed. Because of this, we try to be cautious when adding new fields. + +The same principles apply to [constants and enumerated types](https://kubernetes.io/docs/reference/using-api/deprecation-policy/#enumerated-or-constant-values). diff --git a/content/v1.13-docs/contributing/dns-providers.md b/content/v1.13-docs/contributing/dns-providers.md new file mode 100644 index 0000000000..400f3d17ee --- /dev/null +++ b/content/v1.13-docs/contributing/dns-providers.md @@ -0,0 +1,23 @@ +--- +title: DNS Providers +description: 'cert-manager contributing guide: Creating DNS providers' +--- + +## Creating DNS Providers + +Due to the large number of requests to support DNS providers to resolve DNS +challenges, it became impractical and infeasible to maintain and test all DNS +providers in the main cert-manager repository. + +For this reason, it was decided that new DNS providers should be supported out-of-tree +by way of external webhooks. + +To implement an external DNS provider webhook, it is recommended to base your +implementation on the [cert-manager webhook-example](https://github.com/cert-manager/webhook-example). + +There's further information available in the configuration section: + +- [ACME DNS01 via webhook](../configuration/acme/dns01/README.md#webhook) +- [Configuring an ACME issuer with external webhook](../configuration/acme/dns01/webhook.md) + +If you're struggling with creating a new DNS webhook, reach out on [Slack](./README.md#slack)! diff --git a/content/v1.13-docs/contributing/e2e.md b/content/v1.13-docs/contributing/e2e.md new file mode 100644 index 0000000000..9e6b9e7409 --- /dev/null +++ b/content/v1.13-docs/contributing/e2e.md @@ -0,0 +1,148 @@ +--- +title: Running End-to-End Tests +description: 'cert-manager contribuing guide: End-to-end (E2E) tests' +--- + +cert-manager has an extensive end-to-end (e2e) test suite that verifies functionality against a +real Kubernetes cluster. + +The full end-to-end test suite can take a long time to complete and is run against every pull +request made to the cert-manager project. + +Unless you've made huge changes to the cert-manager codebase --- or to the end-to-end +tests themselves --- you probably don't _need_ to run the tests locally. If you do want to +run the tests, though, this document explains how. + +
        +The status of each commit on the master branch is reported on +[`testgrid.k8s.io`](https://testgrid.k8s.io/jetstack-cert-manager-master). Join the +[`cert-manager-dev-alerts`](https://groups.google.com/g/cert-manager-dev-alerts) +Google group to receive email notifications when tests fail. +
        + +## Requirements + +There are no special requirements for the end-to-end tests. All dependencies can be +provisioned automatically through the make build system. + +## Set up End-to-End Tests + +### Create a Cluster + +You can create a kind cluster using Make: + +```console +# Create a cluster using whatever K8s version is default, named "kind" +make e2e-setup-kind + +# Create a cluster using K8s 1.23 named "keith" +make K8S_VERSION=1.23 KIND_CLUSTER_NAME=keith e2e-setup-kind +``` + +**IMPORTANT:** the kind cluster will be set up using a specific service CIDR range to enable certain functionality in end-to-end tests. This CIDR range is not currently configurable. + +Once complete, the cluster is available via `kubectl` as you'd expect. + +### Install Test Dependencies + +There are various dependencies which the end-to-end tests require, all of which can also +be installed via Make: + +```console +make e2e-setup +``` + +If you only need to update or reinstall one of these dependencies in your test cluster, you can instead install named components explicitly to save some time. + +The most common use case for this is to **reinstall cert-manager itself**, say if you've made a change +locally and want to test that change in a cluster: + +```console +# Most important: reinstall cert-manager, including rebuilding changed containers locally +make e2e-setup-certmanager + +# An example of reinstalling something else; reinstall bind +make e2e-setup-bind + +# More generally, see make/e2e-setup.mk for different targets! +``` + +## Run End-to-End Tests + +As with setup, running tests is available through make. In fact, you can just run `make e2e` directly +and avoid having to set anything up manually! + +```console +# Set up a cluster using the defaults if one's not already present, and then run the end-to-end tests +make e2e + +# Set up a K8s 1.23 cluster and then run tests +make K8S_VERSION=1.23 e2e + +# Run tests exactly as they're run in CI; usually not needed +make e2e-ci +``` + +If you don't want to run every test you can focus on specific tests using `GINKGO_FOCUS` syntax, as described in the +[Ginkgo documentation](https://onsi.github.io/ginkgo/#focused-specs): + +```console +make GINKGO_FOCUS=".*my test description" e2e +``` + +## Cluster IP Details + +As mentioned above, the end-to-end tests expect that certain components are deployed in a +specific way and even at specific IP addresses. + +By way of illustration, the following cluster components are deployed with specific IPs: + +| Component / Make Target | Used in | IP | DNS A Record | +| -------------------------- | -------------------------- | ----------- | --------------------------------------- | +| `e2e-setup-bind` | DNS-01 tests | `10.0.0.16` | | +| `e2e-setup-ingressnginx` | HTTP-01 `Ingress` tests | `10.0.0.15` | `*.ingress-nginx.db.http01.example.com` | +| `e2e-setup-projectcontour` | HTTP-01 `GatewayAPI` tests | `10.0.0.14` | `*.gateway.db.http01.example.com` | + +If you don't set these components up correctly, you might see that the ACME HTTP01 (and other) end-to-end tests fail. + +## End-to-End Test Structure + +The end-to-end tests consist of 2 main parts: issuer specific tests and the conformance suite. + +Both parts use [Ginkgo](https://onsi.github.io/ginkgo/#getting-ginkgo) to run their tests under the hood. + +### Conformance Suite + +#### RBAC + +This suite tests all RBAC permissions granted to cert-manager on the cluster to check that it is able to operate correctly. + +#### Certificates + +This suite tests certificate functionality against all issuers. + +#### Feature Sets + +Some issuers don't support certain features, such as for example issuing Ed25519 certificates or adding an email address +to the X.509 SAN extension. + +Each test specifies a used feature using `s.checkFeatures(feature)`, which is then checked against the issuer's +`UnsupportedFeatures` list. Tests which use a feature unsupported by an issuer are skipped for that issuer. + +### Cloud Provider Tests + +The master branch of cert-manager can also be tested against different cloud providers. Currently, tests for [EKS](https://aws.amazon.com/eks/) are present which run as a periodic job once every two days. + +#### Extending The Cloud Provider Tests + +The infrastructure used to run the e2e tests on cloud providers is present in the [cert-manager/test-infra](https://github.com/cert-manager/test-infra) repository. More cloud providers can be added by creating infrastructure for them using [Terraform](https://www.terraform.io/). + +Apart from that, tests for the existing infrastructure can be customized by editing their respective prow jobs present in the [Jetstack testing repository](https://github.com/cert-manager/testing/tree/master/config/jobs/cert-manager) repository. Values like the cert-manager version or the cloud provider version are present as variables in Terraform so their values can be changed when using `terraform apply` in the prow jobs, for example, for the [EKS prow job](https://github.com/cert-manager/testing/blob/master/config/jobs/cert-manager/cert-manager-periodics.yaml#L524) the cert-manager version being tested can be changed using + +```console +terraform apply -var="cert_manager_version=v1.3.3" -auto-approve +``` + +To see a list of all configurable variables present for a particular infrastructure you can see the `variables.tf` file for that cloud provider's [infrastructure](https://github.com/cert-manager/test-infra). + +> Please note that the cloud provider tests run the e2e tests present in the **master** branch of cert-manager on a predefined version of cert-manager (can be changed in the prow job). Currently, they do **not** test code in a PR, but we have an [issue](https://github.com/cert-manager/cert-manager/issues/4349) tracking that request. diff --git a/content/v1.13-docs/contributing/external-issuers.md b/content/v1.13-docs/contributing/external-issuers.md new file mode 100644 index 0000000000..4d18572fb3 --- /dev/null +++ b/content/v1.13-docs/contributing/external-issuers.md @@ -0,0 +1,77 @@ +--- +title: Implementing External Issuers +description: 'cert-manager contributing guide: External Issuers' +--- + +cert-manager offers a number of [core issuer types](../configuration/README.md) that represent +various certificate authorities. + +Since the number of potential issuers is larger than what could reasonably be supported in the +main cert-manager repository, cert-manager also supports out-of-tree external issuers, and treats +them the same as in-tree issuer types. + +This document is for people looking to _create_ external issuers. For more information on how to +install and configure external issuer types, read the [configuration documentation](../configuration/external.md). + +## General Overview + +An issuer represents a certificate authority that signs incoming certificate +requests. In cert-manager, the `CertificateRequest` resource represents a single +request for a signed certificate, containing the raw certificate request PEM +data as well as other information relating to the desired certificate. + +In cert-manager, each issuer type has its own controller that watches these +`CertificateRequest` resources and checks to see if a given `CertificateRequest` is +configured to use the issuer. + +This is done via the `issuerRef` stanza on the `CertificateRequest` which contains +an issuer `name`, `kind` and `group`. + +`group` denotes an API group such as `cert-manager.io` (which is responsible for all core issuer types). + +`kind` denotes the "kind" resource type of the issuer - usually `Issuer` or `ClusterIssuer`. + +`name` denotes the name of the issuer resource of the specified kind. An example might be `my-ca-issuer`. + +When an issuer controller observes a new `CertificateRequest` which refers to it, +it then ensures that the corresponding issuer resource exists in Kubernetes. + +It then uses the information inside the issuer resource to attempt to create a +signed certificate, based upon the information inside the certificate request. + +## Sample External Issuer + +If you want to create an External Issuer, the best place to start is likely to be the [Sample External Issuer](https://github.com/cert-manager/sample-external-issuer). + +The Sample External Issuer is maintained by the cert-manager team, and its README file has step-by-step instructions +on how to write an external issuer using Kubebuilder and controller-runtime. + +## Approval + +Before signing a certificate, Issuers **must** also ensure that the `CertificateRequest` is +[`Approved`](../concepts/certificaterequest.md#approval). + +If the `CertificateRequest` is not `Approved`, the issuer **must** not process it. Issuers are not +responsible for approving `CertificateRequests` and should refuse to proceed if they find a certificate +that is not approved. + +If a `CertificateRequest` created for an issuance associated with a `Certificate` gets [`Denied`](../concepts/certificaterequest.md#approval), the issuance will be failed by cert-manager's issuing controller. + +## Conditions + +Once a signed certificate has been gathered by the issuer controller, it updates the status of the +`CertificateRequest` resource with the signed certificate. It is then important to update the condition +status of that resource to a ready state, as this is what is used to signal to higher order +controllers - such as the `Certificate` controller - that the resource is ready to be consumed. + +Conversely, if the `CertificateRequest` fails, it is as important to mark the resource as such, as this will +also be used as a signal to higher order controllers. Valid condition states are listed under [concepts](../concepts/certificaterequest.md#conditions). + +## Implementation + +It is recommended that you make use of the [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder) project in order +to implement your external issuer controller. This makes it very simple to generate `CustomResourceDefinitions` and gives +you a lot of controller functionality out of the box. + +If you have further questions on how to implement an external issuer controller, it is best to reach out on [slack](./README.md#slack) +or to join a [community calls](./README.md#meetings). diff --git a/content/v1.13-docs/contributing/featuregates.md b/content/v1.13-docs/contributing/featuregates.md new file mode 100644 index 0000000000..4e6f332967 --- /dev/null +++ b/content/v1.13-docs/contributing/featuregates.md @@ -0,0 +1,47 @@ +--- +Title: Implementing feature gates +description: 'cert-manager contributing guide: Implementing feature gates' +--- + +As of v1 release cert-manager is considered stable. We aim to follow Kubernetes API compatibility policy when making API changes, see [API compatibility](../installation/api-compatibility.md) to avoid breaking users' existing cert-manager installations. +This means that as developers we are somewhat limited in regards to changing existing behavior, i.e renaming or removing API elements or changing their behavior. + +New functionality that is not yet stable[^1] can still be added, but it needs to be placed behind a feature gate. + +## Feature gated API fields + +Feature gated API fields are implemented using `--feature-gates` flags of cert-manager [webhook](../cli/webhook.md) and [controller](../cli/controller.md). + +A feature gated API field is always visible to the user (i.e when running `kubectl explain `), but is only functional if the relevant feature is explicitly enabled via feature flags for both the webhook and controller. + +If a user attempts to apply a resource with the feature gated field set to a non-nil value, but the feature gate is not enabled, the resource will get rejected by the webhook validation. +This mechanism differs from [the one that Kubernetes uses for feature gated API field implementation](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md#new-field-in-existing-api-version) where the field will be simply set to nil if the feature gate is disabled. We chose to use webhook validation instead to make debugging easier for users who are attempting to use the feature gated field, but have forgotten to enable the feature gate. + + +### Implementation + +- Implement the new field and document that it is feature gated and in order to use it the controller and webhook feature gates need enabling +- Add a new [webhook feature gate](https://github.com/cert-manager/cert-manager/blob/3a055cc2f56c1c2874807af4a8f84d0a1c46ccb4/internal/webhook/feature/features.go#L25-L39) for the field +- Update webhook validation checks for the relevant resource kind to ensure that if the feature gated field is set, but the webhook feature gate is not enabled, the resource gets rejected +- Add a new [controller feature gate](https://github.com/cert-manager/cert-manager/blob/2417132b3cd017b5f0974006e03c2b8a540efe3f/internal/controller/feature/features.go#L26-L54) for the field +- Ensure that any control loops that use the feature, check that the feature gate is actually enabled. (This is required to cover edge cases such as if the webhook runs a version of cert-manager where the feature is in GA whereas controller runs an older version where the feature is still in experimental state) +- Ensure that the feature gate is added to cert-manager installation scripts for CI and local tests in [make](https://github.com/cert-manager/cert-manager/blob/134398e939bb2b1401697eaf589405ad469cd609/make/e2e-setup.mk#L165) and [bazel](https://github.com/cert-manager/cert-manager/blob/fd747b42b9ab4b6409b61b7946e8dc14d532e950/devel/addon/certmanager/install.sh#L26) scripts +- Default cert-manger e2e CI tests run with all feature gates for all components enabled. There is an additional optional e2e test that runs with all feature gates disabled. You can trigger that for your PR with `/test pull-cert-manager-e2e-feature-gates-disabled` to verify that all works as expected both with and without the new feature gate. + +### Potential issues + +- The person deploying cert-manager has to remember to set two cert-manager feature gates, one of the webhook one on the controller for the feature to function. Forgetting to set one of them might result in unexpected behavior + +- A user must remember to remove the alpha fields from their manifests when disabling a previously enabled API feature. Failing to do so might result in unexpected behavior- for example forgetting to remove feature gated field from a `Certificate` resource might result in failed renewals at some later point when cert-manager's controller will attempt to update the `Certificate` spec, but the webhook will reject the update due to the feature gated field being set. + +### References + +- cert-manager's [API compatibility promise](../installation/api-compatibility.md) + +- An example implementation of an alpha field is [`AdditionalOutputFormats` field on `Certificate` spec](https://github.com/cert-manager/cert-manager/blob/dbad3d98f3d7d85cadb4bd2c2493faf8b666b313/internal/apis/certmanager/types_certificate.go#L169-L174) + +- [Kubernetes definition of feature stages](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages) + +- Kubernetes [API change design](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md) + +[^1]: For example, functionality that might change in the future in response to user feedback diff --git a/content/v1.13-docs/contributing/google-season-of-docs/2022/README.md b/content/v1.13-docs/contributing/google-season-of-docs/2022/README.md new file mode 100644 index 0000000000..587036e7e1 --- /dev/null +++ b/content/v1.13-docs/contributing/google-season-of-docs/2022/README.md @@ -0,0 +1,10 @@ +--- +title: Google Season of Docs 2022 +description: Google season of docs 2022 proposal +--- + +We registered our interest to participate in Google Season of Docs 2022! + +There's one project proposal: + +[Improve the Navigation and Structure of the cert-manager Website](./improve-navigation-and-structure.md) diff --git a/content/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md b/content/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md new file mode 100644 index 0000000000..496ef1be33 --- /dev/null +++ b/content/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md @@ -0,0 +1,269 @@ +--- +title: Improve the Navigation and Structure of the cert-manager Website +description: Google season of docs 2022 proposal +--- + +## Project Updates + +### 7 Sept 2022: The Webhook Debugging Guide + +friction log for task 3, before +friction log for task 3, after + +At the start of the Google Season of Docs program, we built friction logs for +common user tasks, such as debugging the error "connect: connection refused". +The friction log for this task, visible in the [GSoD work +document](https://docs.google.com/document/d/1O-MFWwtpOcNlrRzsiBvrpGHC10EXnvw0XK37M2nEjzg/edit#bookmark=id.cu9ss8s7yl46), +was to serve as a reference point to see whether the improvements we aimed to +bring would have an impact or not. + +The friction log showed a consistent pattern: the user searches the error on +Google, is confused by GitHub issues that don't have any solutions, then clicks +the second link in the Google results, without much luck. We realized that one +improvement we could make was to add a link to the FAQ page "Troubleshooting +Problems with the Webhook". We found two problems with this FAQ page: + +1. It could not be found by anyone because the error messages were not listed in + the page, meaning that Google would not show the page in the search results. +2. Many error messages were not listed in the page. + +We set ourselves to rewrite this page with the goal of making it error-focused, +meaning that the user would just be able to look for their particular error and +start debugging it. We called it "The Definitive Debugging Guide for the +cert-manager Webhook Pod", and it can be found +[here](../../../troubleshooting/webhook.md). + +### 12 Aug 2022: Improved the layout of the navigation menu + +On displays `>=1280px` the left-hand menu was too narrow to display the nested menu items clearly, +On smaller displays the [responsive CSS](https://tailwindcss.com/docs/responsive-design) actually made the menu larger. +So we've widened it by 1 column on displays `>=1280px` and reduced the width of the content by 1 column to compensate. +This makes the menu much easier to read on laptop and desktop computer screens. + +We fixed an inconsistency in the vertical spacing between menu items with sub-menus and those without. + +And finally, we moved the version selector to the bottom of the side-bar to avoid distracting the reader. + +### 3 August 2022: The cert-manager.io Documentation Survey is now closed + +Thank you to everyone who participated in our documentation survey. +We will use the results to prioritize sections of the website for restructuring and rewriting. +Before the conclusion of this Season-of-Docs we will select a random winner from among the responses and contact you about your prize. + +### 18 July 2022: The cert-manager.io Documentation Survey + +Screenshot 2022-07-18 at 14-35-48 cert-manager documentation survey + +We have created a short survey, to help us identify what are the top-priorities for the cert-manager.io documentation. + +1. We want identify the most useful documentation, so that we don't go and change things that are already working well. +2. We want to know which documentation is not useful, so that we can make improvements. +3. We'd like to hear from new and experienced users about how and how often you use the documentation. +4. And we'd like to know where else you find good information about cert-manager, outside of the cert-manager.io website, +so that we can try and incorporate some of those sources. + +We've added a link to the survey to the banner at the top of this site +and we will also be sharing the link in our Slack channels and mailing lists. + +[Please take 10 minutes to fill in the survey](https://docs.google.com/forms/d/e/1FAIpQLSeqfRkd86_N0L7VOW_ImCT0iyUabhczdiDk2dQDLp55V8kqvw/viewform). + +
        + +### 15 July 2022: New "Getting Started" pages + + + + +We have been auditing the existing documentation to identify some key tasks that our users and potential new users need to carry out. +We have created "friction logs" for some of these tasks. +What this means is that we imagine ourselves in the place of the user and ask, for example, + +> How can I get a Let's Encrypt certificate for my server in Kubernetes? + +So we searched Google and DuckDuckGo for "Let's Encrypt Kubernetes" and to our surprise, cert-manager.io does not feature among the top search results. + +Among the results are some excellent third-party tutorials and videos about using cert-manager to create Let's Encrypt certificates, +and we are grateful to the authors for taking the time to write such detailed content. +But inevitably, some of these refer to much older versions of cert-manager and Kubernetes. +So we have decided to write some official guides, for the cert-manager.io website which demonstrate how to quickly install cert-manager and configure it for Let's Encrypt. +We hope that in time these will be indexed by the search engines and that they will reach the top of the search results for "Let's Encrypt Kubernetes". +The advantages will be that users and potential users will find up-to-date information, +and the cert-manager.io maintainers will receive fewer support requests from new users who are attempting this task. + +Go and read the new [Getting Started Guide for GKE Users](../../../getting-started) and tell us what you think. + +
        + +### 5 May 2022: Announcing Mehak Saeed as Technical Writer + +We are delighted to announce that [Mehak Saeed](https://www.linkedin.com/in/mehak-saeed-29121a12a) will be the technical writer working on this project. +We were extremely impressed with Mehak's presentation during her interview and impressed with her detailed preparations and planning. +We look forward to working with her. + +Thank you to all the other technical writers who applied for this project. + +### 14 April 2022: Project Accepted + +This project was [accepted on 14 April 2022](https://developers.google.com/season-of-docs/docs/participants). + +### 24 March 2022: Project Registered + +We have [registered our interest to participate in Google Season of Docs 2022](https://github.com/google/season-of-docs/pull/483), +and have submitted a single project proposal detailed in the remaining of this +page. + +You have until 27 April 2022 18:00 UTC to apply for the technical writer role. + +We will be sharing the name of the selected candidate on Wed 4 May 2022 at +15:00 London Time (14:00 UTC) on Slack in the channel `#cert-manager-dev`. + +To apply as a technical writer, please let us know by one of the two ways +below: + +- e-mail us at `cert-manager-maintainers@googlegroups.com` with the prefix + `GSoD2022:` in the e-mail subject. +- or open an issue on + [cert-manager/website](https://github.com/cert-manager/website) with the + prefix `GSoD2022:` in the issue title. + +You can join our open standup (every day at 10:30 UK time), and join the +Kubernetes Slack channel `#cert-manager-dev` to know more about this project +proposal. + +## About cert-manager + +cert-manager (current version 1.8.0, first release in October 2017) is an Apache-2.0 licensed Kubernetes add-on to automate the management and issuance of TLS certificates. + +Our typical contributors are Go developers from around the world with experience of the Kubernetes ecosystem with experience contributing to core Kubernetes components and Kubernetes operators. + +Our users are often developers and system administrators who are trying to automate the rotation of TLS certificates for applications running in their Kubernetes clusters. + +Our largest users have cert-manager installed on multiple Kubernetes clusters and managing many thousands of TLS certificates. + +## Project Overview + +Right now the content is not designed with our target audiences in mind. +For example a new user will not easily find a guide explaining how to install cert-manager on AWS and configure it for Let’s Encrypt. +Nor will a Cluster Administrator easily find information about how to optimize cert-manager for a large cluster with many Certificates. +The information exists but is spread across multiple pages and is often not at the obvious page. + +As a visual example, a user looking for a guide on how the Certificate resource can be used may feel helpless when realizing that the "Certificate" page exists twice: once under the "Usage" section, and once under the "Concepts" section. + +![Screenshot of the cert-manager.io website with Usage and Concepts visible in the menu](/images/google-season-of-docs-2022-improve-navigation-and-structure.png) + +(NB: This screenshot is from our old site design but the text and layout are broadly the same) + +We would like a technical writer: + +1. to help us identify our target audiences, and +2. to identify the key tasks of each of these audiences, and +3. re-structure the cert-manager.io website with this in mind. + +For example, we have discussed the following audiences and tasks: Beginner, Cluster Administrator, User, Integrator, New Contributor +and each of these people will be interested in a different set of tasks. +We would like them to quickly and easily find the information they need. + +By making it easier for each group to find the information they need we aim to reduce the number of support queries. + +## Audiences + +### New User + +Has never used cert-manager and may never have used Kubernetes. +Wants to find out what cert-manager can offer. +May have heard about cert-manager in another tutorial. +May want to know what are the alternatives to cert-manager and the trade offs. +Needs to install cert-manager quickly so that they evaluate it on their laptop. +Needs to learn basic configuration of cert-manager. +Needs to understand what are the next steps. + +### Ongoing User + +A programmer who wants to deploy a TLS protected APP. +Knows that cert-manager has been installed by their cluster administrator. +Has an existing Issuer or ClusterIssuer. +Needs to know how to create a Certificate which is appropriate for their application. E.g. +* Create a certificate for their PostgreSQL database +* Create an certificate for their Ingress / Gateway +Needs to know how to debug why their certificate hasn’t renewed +Needs to understand the error messages on cert-manager Certificates and Certificate requests +Needs to know which errors they can fix and which errors require assistance from their cluster administrator. + +### Cluster Administrator + +Knows Kubernetes. +Has a long running cert-manager installation. +Wants to know how to configure it and upgrade it for optimum performance. +Wants to optimize for large numbers of certificates. +Wants to upgrade from older versions. +Wants to monitor cert-manager performance +Wants to set up alerts to notify them when cert-manager goes wrong. +May want to configure cert-manager for multiple cloud providers. +May want to get cert-manager working with some other cluster scoped system like Istio or Knative. + +### Integrator + +May want to allow cert-manager users to make use of a custom Certificate service. +May want to integrate cert-manager with a DNS API for ACME DNS01. +May want to depend on cert-manager for managing TLS certificates for a higher level system. +Needs to learn how to write plugins / extensions for cert-manager. +Needs links to state-of-the-art examples of plugins and extensions. + +### New Contributor + +Wants to report a bug in cert-manager. +Wants to fix a bug in cert-manager. +Wants to suggest a feature for cert-manager. +Wants to implement a feature for cert-manager. +Needs to learn how to navigate the cert-manager code. +Learn cert-manager coding standards and house style. +Needs to know how to run the tests for cert-manager. + +## Scope + +The scope of this project is as follows: + +1. Identify and describe three target audiences. +2. Identify three key top tasks for each of these audiences. +3. Audit the existing documentation and create a friction log of the current documentation. +4. Using the friction log as a baseline, re-organize the documentation to minimize friction for three top tasks. +6. Incorporate feedback from documentation testers (volunteers in the project) and the wider cert-manager community. +7. Work with the cert-manager team to publish the documentation on cert-manager.io. +8. Create documentation for website contributors explaining how we structure our content around audiences and tasks. + +## Measuring success + +After the technical writer has helped us identify the 3 key tasks for each audience +we will measure a baseline number of clicks required to achieve the task and we will aim to minimize the number of clicks for each task. + +## Timeline + +| Dates | Action Items | +|-------------|--------------------------------------------------| +| May | Orientation | +| May / June | Identify audiences and tasks | +| May / June | Audit and friction log | +| June | Restructuring tasks | +| June / July | Incorporating feedback | +| June / July | Publish to cert-manager.io | +| July | Finish writing guidance for website contributors | +| July | Project Completion | + +## Budget + +| Budget item | Amount ($) | Running Total ($) | Notes | +|-------------------------------------------------------------------------------|-------------|-------------------|---------------------------------| +| Technical writer audit and restructuring of the cert-manager.io documentation | 12,000 | 12,000 | | +| Volunteer stipends | 1,500 | 13,500 | 3 volunteer stipends x 500 each | +| TOTAL | | 13,500 | | + +Regarding the amount of $12,000, we assume that it will be enough to fund one experienced technical writer +part-time (for example, they could work half day from Tuesday to Friday, for a total of 24 days, for 3 months +at a daily rate of $500). + +We will give the "volunteer stipend" to contributors who can show they have one PR within the project +time frame (from 1st May to 30th July) in which a re-write of one page or a set of pages. Before +starting the rewriting, the volunteer will suggest which page they wish to work on either on Slack +(Kubernetes Slack, channel #cert-manager-dev), or in an issue on GitHub, and make sure by asking the +team whether it makes sense to rework this page. As long as at least one positive reaction, the +volunteer can start working. For the stipend to be validated, the PR needs to be reviewed and merged. diff --git a/content/v1.13-docs/contributing/google-season-of-docs/README.md b/content/v1.13-docs/contributing/google-season-of-docs/README.md new file mode 100644 index 0000000000..cb43f69aa8 --- /dev/null +++ b/content/v1.13-docs/contributing/google-season-of-docs/README.md @@ -0,0 +1,8 @@ +--- +title: Google Season of Docs +description: cert-manager and Google Season of Docs +--- + +The cert-manager project participated in Google Season of Docs 2022 + +If you're interested in what happened, you can check out our [2022 proposals!](./2022/README.md). diff --git a/content/v1.13-docs/contributing/importing.md b/content/v1.13-docs/contributing/importing.md new file mode 100644 index 0000000000..2c39a247e3 --- /dev/null +++ b/content/v1.13-docs/contributing/importing.md @@ -0,0 +1,42 @@ +--- +title: Importing cert-manager in Go +description: 'cert-manager contributing guide: Importing cert-manager' +--- + +cert-manager is written in Go, and uses Go modules. You _can_ import it as a Go module, and in some cases +that's fine or even encouraged, but as a rule we generally recommend against importing cert-manager. + +Generally speaking, except for the cases listed below under [When You Might Import cert-manager](#when-you-might-import-cert-manager), +code in the cert-manager repository is *not* covered under any Go module compatibility guarantee. We can and will make breaking +changes, even in publicly exported Go code and even in a minor or patch release of cert-manager. We have made breaking changes like +this in the past. + +Note that this doesn't affect _running_ cert-manager. Our commitment on compatibility is to not break the runtime +functionality of cert-manager, and we take that seriously. + +If you're certain that you *do* need to import cert-manager as a module, see [Module Import Paths](#module-import-paths) +below for a note on how to do that. + +## When You Might Import cert-manager + +You might need to import cert-manager if you're writing Go code which: + +- uses cert-manager custom resources, so you want to import something under `pkg/apis` +- implements an external DNS solver webhook, as in the [webhook-example](https://github.com/cert-manager/webhook-example) +- implements an external issuer, as in the [sample-external-issuer](https://github.com/cert-manager/sample-external-issuer) + +If you think you really need to import other parts of the code, please do reach out and [talk to us](./README.md#slack) so we're +aware of this need! We'll always try to avoid breakage where we can. + +## Module Import Paths + +For all supported versions of cert-manager, the module import path is `github.com/cert-manager/cert-manager`. + +Historically, the cert-manager repository was created on GitHub as `https://github.com/jetstack/cert-manager`, and was later +migrated to `https://github.com/cert-manager/cert-manager`. + +This means that the Go module import path you need may be different if you're trying to use an older version of cert-manager. + +For cert-manager 1.8 and later, use the new path listed above. + +For cert-manager versions older than 1.8 use the old path: `github.com/jetstack/cert-manager` diff --git a/content/v1.13-docs/contributing/kind.md b/content/v1.13-docs/contributing/kind.md new file mode 100644 index 0000000000..a6fc15c14a --- /dev/null +++ b/content/v1.13-docs/contributing/kind.md @@ -0,0 +1,31 @@ +--- +title: Developing with Kind +description: 'cert-manager contributing guide: Using Kind' +--- + +[Kind](https://kind.sigs.k8s.io/) allows you to provision Kubernetes clusters locally using nested Docker containers, +with no requirement for virtual machines. + +These clusters are quick to create and destroy, and are useful for simple testing for +development. cert-manager also uses kind clusters in its [end-to-end tests](./e2e.md). + +## Using Kind Locally + +You should be able to make use of cert-manager's end-to-end test setup logic to create a local Kind cluster for +development. As such, if you want a local cluster you might want to follow some of the details in the +[end-to-end test documentation](./e2e.md). + +If, though, you just want to get a cluster up and running with your local changes to cert-manager running inside +`kind`, try the following: + +```console +make e2e-setup-kind e2e-setup-certmanager +``` + +Or, if you need a specific version of Kubernetes: + +```console +make K8S_VERSION=1.xx e2e-setup-kind e2e-setup-certmanager +``` + +That should leave you with a working cluster which you can interact with using `kubectl`! diff --git a/content/v1.13-docs/contributing/policy.md b/content/v1.13-docs/contributing/policy.md new file mode 100644 index 0000000000..d29f34e41f --- /dev/null +++ b/content/v1.13-docs/contributing/policy.md @@ -0,0 +1,159 @@ +--- +title: Feature Policy +description: 'cert-manager contributing guide: Contribution Policy' +--- + +We love to receive both feature requests and PRs which add to and improve cert-manager; the community is at the heart of what we do! + +If you're thinking of adding a feature, we recommend you read this doc to maximize the chances of your contribution getting the attention it deserves and hopefully to get it merged quickly! + +We recommend creating an issue first for it to be discussed with the cert-manager maintainers. Another possibility is bringing it up in a community meeting for an open discussion on the implementation. + +## Feature Sizing: Getting Your Change Accepted + +We evaluate new features and PRs based on their size and their significance; either they're small or large. + +### Smaller Features + +Many contributions are small. That usually - but not always - means that implementing them won't require many lines of code to be added or changed, and in any case they should be easy +for maintainers to review. A PR being small is a good thing; if you can down-scope your feature to make it smaller, we won't complain! + +If you believe your feature is small, please feel free to just raise a PR and optionally also post a link to your PR in the [cert-manager-dev slack channel](./README.md#slack). Usually a sufficiently small PR can be merged without too much ceremony. If we think it's actually a larger piece of work, we'll let you know. + +### Larger Features + +If you're not sure whether your PR is small, or if you know it's bigger, you'll want to speak to us first before raising a PR. This +will help to ensure that your PR is something we're likely to merge to avoid wasting your time. It'll also make it easier +for us to do the design process. + +#### Design Documents + +Larger feature development should normally start with a design discussion. To get that started, you would raise a PR with a design document against [cert-manager/cert-manager/design](https://github.com/cert-manager/cert-manager/tree/master/design). This allows us to discuss the proposed functionality before starting the work to implement it and serves as a way to document the decisions and reasoning behind them. Ideally, a good design document should allow for faster and more consistent feature development and implementation process by providing a single place where all potential concerns and questions are answered. + +We have a [design template](https://github.com/cert-manager/cert-manager/blob/master/design/template.md) that outlines the structure of the document. +(This is a simplified version of [Kubernetes enhancements KEP template](https://github.com/kubernetes/enhancements/tree/master/keps/NNNN-kep-template)). +Do reach out if you need help with the design. + +Part of the process of discussing a design document may also include a video call with you included! That helps us to plan how a feature should +be implemented and approached. It'll be pretty informal and casual; we just want to make sure we're all on the same page. This call might be part +of a biweekly meeting. + +#### Making Progress with Larger Features + +Larger features with a design document are much more likely to be accepted, and in turn we're much more likely to commit a single +named cert-manager maintainer to the effort to help the PR to be successful. That maintainer might not be able to answer all your +questions, but they should certainly be able to point you in the right direction. + +To get in touch to discuss a feature, please reach out on the [cert-manager-dev slack channel](./README.md#slack), or join a [cert-manager public meeting](./README.md#meetings) to talk about your proposal. + +If you have an open PR with a design document (or have some questions about how to proceed with a design), you should absolutely feel free to add the PR with your design or a link to the relevant GitHub issue to the [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U/edit) for our next biweekly meeting +and join in so we're sure to discuss it and so you can contribute to the discussion! + +#### Large Feature Lifecycle + +1. Informally ask about the feature in slack or a public meeting +2. Create a PR with a lightweight design document using the [design template](https://github.com/cert-manager/cert-manager/tree/master/design/template.md), for discussion +3. Design doc PR gets reviewed - possibly includes meeting or discussion in a biweekly meeting +4. Implement your feature, helped and reviewed by a named cert-manager maintainer + +## Feature Requests We'll Likely Reject + +In some cases, people will request features which we've previously rejected or which for some reason we have to reject. + +It's nothing personal; sometimes we have to make tough choices and especially when it comes to security and maintainability we have to reject certain +proposals. If your feature request is listed below, there's a high chance we'll have to reject it. + +That said, if you think we've made a mistake and that we should reconsider, we're open to chatting - consider joining our [biweekly meetings](./README.md#meetings) to discuss it with us! + +### Vendoring Kubernetes related APIs outside of the `k8s.io/` namespace + +Vendoring project APIs that also vendor `k8s.io/apimachinery`, such as OpenShift, Contour, or Velero, is not recommend because the Kubernetes dependency is likely to conflict with cert-manager's instance. +It could also cause a conflict with different Kubernetes client versions being used. + +If this is needed it is suggested to use a "dynamic client" that converts the objects into internal structures copied into the cert-manager codebase. + +### Additional configuration options for the Helm chart + +cert-manager's Helm chart is intended to allow to create a standard, best practices cert-manager installation with basic configuration options, such as being able to provide flags to cert-manager components, label resources etc. +We do not aim to include every possible configuration option for resources that the chart creates to avoid maintenance burden and because we do not have automated testing for all chart configuration options. Therefore we are likely to not accept PRs that add advanced or niche configuration options to Helm charts- we recommend that users who require that configuration use another mechanism such as [Helm's post-install hooks](https://helm.sh/docs/topics/charts_hooks/). + +### Helm + CRDs + +Helm suggests that CRDs be included in a `crds/` subdirectory of a chart, with the `crd-install` annotation included. This has the unfortunate side effect that CRDs are not upgraded if changed in a later release. + +CRDs being upgraded without being removed and re-installed is essential for cert-manager to move forward. + +This was previously discussed [in the Helm community](https://github.com/helm/helm/issues/5871). + +cert-manager works around this limitation by shipping CRDs in the templates. + +### Helm Subchart capabilities + +cert-manager now has the capability to be [installed as a subchart](../installation/helm.md#installing-cert-manager-as-subchart). + +But you need to be careful when adding it to your umbrella chart. + +This is because the cert-manager installation creates cluster scoped resources like admission webhooks and custom resource definitions. cert-manager should be seen as part of your cluster and should be treated as such for being installed. An apt comparison +to other Kubernetes components would be a LoadBalancer controller or a PV provisioner. + +It is your responsibility to ensure that cert-manager is only installed once in your cluster. +This can be managed via the `condition` parameter of the dependency in your `Chart.yaml`, which allows users to disable the installation of a subchart. The condition parameter must be added when using cert-manager as a subchart to allow users to disable your dependency. + +```yaml +apiVersion: v2 +name: example_chart +description: A Helm chart with cert-manager as subchart +type: application +version: 0.1.0 +appVersion: "0.1.0" +dependencies: + - name: cert-manager + version: v1.8.0 + repository: https://charts.jetstack.io + alias: cert-manager + condition: cert-manager.enabled +``` + +### Secret injection or copying + +cert-manager deals with very sensitive information (all TLS certificates for your services) and has cluster-level access to secret resources. As such, when designing features we need to consider all of the ways these secrets might be abused to escalate privilege. + +Secret data is meant to be securely stored in `Secret` resources and have narrow scoped access privileges for unauthorized users. Because of this, we won't usually add any functionality that allows this data to be copied/injected into any resource +other than a Kubernetes `Secret`. + +#### cainjector + +The cainjector component is a special exception to this rule as it deals in non-sensitive information (CAs, not cert/key pairs). This component is able to inject the `ca.crt` file into predefined fields on `ValidatingWebhookConfiguration`, `MutatingWebhookConfiguration`, and `CustomResourceDefinition` resources from Certificate resources. + +These 3 components are already scoped only for privileged users, and will already give you cluster scoped access to resources. + +If you’re designing a resource that needs a CA Certificate or TLS key pair it is strongly recommended to use a reference to a secret instead of embedding it in a resource. + +### Cross namespace resources + +Namespace boundaries in Kubernetes provide a barrier for access scopes. Apps or users can be limited to only access resources in a certain namespace. + +cert-manager is a controller that operates on cluster wide resources however, and while it may seem interesting to allow access to copy or write certificate data from one namespace to the other, this can cause a bypass of the +namespace security model for all users, which is usually not intended and can be a major a security issue. + +We don't support this behavior; if you believe you need it, and it's intended for your use case then there are other Kubernetes controllers that can do this, although we'd suggest extreme caution. + +### Sign certificates using the Kubernetes CA + +Kubernetes has a Certificate Signing Requests API, and a `kubectl certificates` command which allows you to approve certificate signing requests and have them signed by the certificate authority (CA) of the Kubernetes cluster. This +CA is generally used for your nodes. + +This API and CLI have occasionally been misused to sign certificates for use by pods outside of the control plane; we believe this is a mistake. + +For the security of the Kubernetes cluster it's important to limit access to the Kubernetes certificate authority; such certificates increase the attack surface for the Kubernetes API server since this CA signs certificates for +authorization against the API server. If cert-manager used this cert, it could allow any user with permission to create cert-manager resources to elevate privileges by signing certificates which are trusted for API access. + +[See our FAQ](../faq/README.md#kubernetes-has-a-builtin-certificatesigningrequest-api-why-not-use-that) for more details on this. + +### Integrations with third party infrastructure providers + +We try to not include in core cert-manager new functionality that involves calling third party APIs that we don't have infrastructure to test (or that the maintainers don't have the skills to work with). + +Instead we try to build interfaces such as [external DNS webhook solver](../configuration/acme/dns01/webhook.md) that can be implemented to use cert-manager with a particular third party implementation. +We believe that this is a more sustainable approach as that way folks who have knowledge and skills to work with particular infrastructure can own a project that interacts with it and it lets us avoid merging potentially untested code to core cert-manager. +An example of a PR that might be rejected would be adding a new external DNS solver kind, see https://github.com/cert-manager/cert-manager/pull/1088 diff --git a/content/v1.13-docs/contributing/release-process.md b/content/v1.13-docs/contributing/release-process.md new file mode 100644 index 0000000000..30d273485a --- /dev/null +++ b/content/v1.13-docs/contributing/release-process.md @@ -0,0 +1,735 @@ +--- +title: Release Process +description: 'cert-manager contributing: Release process' +--- + +This document aims to outline the process that should be followed for +cutting a new release of cert-manager. If you would like to know more about +current releases and the timeline for future releases, take a look at the +[Supported Releases](../installation/supported-releases.md) page. + +## Prerequisites + +⛔️ Do not proceed with the release process if you do not meet all of the +following conditions: + +1. The relevant [testgrid dashboard](https://testgrid.k8s.io/cert-manager) should not be failing for the release you're trying to perform. +2. The release process **takes about 40 minutes**. You must have time to complete all the steps. +3. You currently need to be at Jetstack to get the required GitHub and GCP + permissions. (we'd like contributors outside Jetstack to be able to get + access; if that's of interest to you, please let us know). +4. You need to have the GitHub `admin` permission on the cert-manager project. + To check that you have the `admin` role, run: + + ```bash + brew install gh + gh auth login + gh api /repos/cert-manager/cert-manager/collaborators/$(gh api /user | jq -r .login)/permission | jq .permission + ``` + + If your permission is `admin`, then you are good to go. To request the + `admin` permission on the cert-manager project, [open a + PR](https://github.com/jetstack/platform-board/pulls/new) with a link to + here. + +5. You need to be added as an "Editor" to the GCP project + [cert-manager-release](https://console.cloud.google.com/?project=cert-manager-release). + To check if you do have access, try opening [the Cloud Build + page](https://console.cloud.google.com/cloud-build?project=cert-manager-release). + To get the "Editor" permission on the GCP project, open a PR with your name + added to the maintainers list in + [`cert_manager_release.tf`](https://github.com/jetstack/terraform-jetstack/blob/master/cert_manager_release.tf) + + ```diff + --- a/cert_manager_release.tf + +++ b/cert_manager_release.tf + @@ -17,6 +17,7 @@ locals { + var.personal_email["..."], + var.personal_email["..."], + var.personal_email["..."], + + var.personal_email["mael-valais"], + ]) + } + ``` + + You may use the following PR description: + + ```markdown + Title: Access to the cert-manager-release GCP project + + Hi. As stated in "Prerequisites" on the [release-process][1] page, + I need access to the [cert-manager-release][2] project on GCP in + order to perform the release process. Thanks! + + [1]: https://cert-manager.io/docs/contributing/release-process/#prerequisites + [2]: https://console.cloud.google.com/?project=cert-manager-release + ``` + +This guide applies for versions of cert-manager released using `make`, which should be every version from cert-manager 1.8 and later. + +**If you need to release a version of cert-manager 1.7 or earlier** see [older releases](#older-releases). + +First, ensure that you have all the tools required to perform a cert-manager release: + +1. Install the [`release-notes`](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md) CLI: + + ```bash + go install k8s.io/release/cmd/release-notes@v0.13.0 + ``` + +2. Install our [`cmrel`](https://github.com/cert-manager/release) CLI: + + ```bash + go install github.com/cert-manager/release/cmd/cmrel@latest + ``` + +3. Clone the `cert-manager/release` repo: + + ```bash + # Don't clone it from inside the cert-manager repo folder. + git clone https://github.com/cert-manager/release + cd release + ``` + +4. Install the [`gcloud`](https://cloud.google.com/sdk/) CLI. +5. [Login](https://cloud.google.com/sdk/docs/authorizing#running_gcloud_auth_login) + to `gcloud`: + + ```bash + gcloud auth application-default login + ``` + +6. Make sure `gcloud` points to the cert-manager-release project: + + ```bash + gcloud config set project cert-manager-release + export CLOUDSDK_CORE_PROJECT=cert-manager-release # this is used by cmrel + ``` + +7. Get a GitHub access token [here](https://github.com/settings/tokens) + with no scope ticked. It is used only by the `release-notes` CLI to + avoid API rate limiting since it will go through all the PRs one by one. + +## Minor releases + +A minor release is a backwards-compatible 'feature' release. It can contain new +features and bug fixes. + +### Release schedule + +We aim to cut a new minor release once per month. The rough goals for each +release are outlined as part of a GitHub milestone. We cut a release even if +some of these goals are missed, in order to keep up release velocity. + +### Process for releasing a version + +
        +🔰 Please click on the **Edit this page** button on the top-right corner of this +page if a step is missing or if it is outdated. +
        + +1. Remind yourself of our release terminology by looking at the following table. + This will allow you to know which steps to skip by looking the header of the + step, e.g., **(final release only)** means that this step must only be + performed when doing a final release. + + | Type of release | Example of git tag | + |------------------------------------|--------------------| + | initial alpha release | `v1.3.0-alpha.0` | + | subsequent alpha release | `v1.3.0-alpha.1` | + | initial beta release | `v1.3.0-beta.0` | + | subsequent beta release | `v1.3.0-beta.1` | + | final release | `v1.3.0` | + | (optional) patch pre-release[^1] | `v1.3.1-beta.0` | + | patch release (or "point release") | `v1.3.1` | + + [^1]: One or more "patch pre-releases" may be created to allow voluntary community testing of a bug fix or security fix before the fix is made generally available. The suffix `-beta` must be used for patch pre-releases. + +2. Set the 4 environment variables by copying the following snippet in your + shell table: + + ```bash + export RELEASE_VERSION="v1.3.0-alpha.0" + export START_TAG="v1.2.0" + export END_REV="release-1.3" + export BRANCH="release-1.3" + ``` + + > **Note:** To help you fill in the correct values, use the following + > examples: + > + > | Variable | Example 1 | Example 2 | Example 2 | Example 3 | Example 4 | + > | ----------------- | ---------------- | ---------------- | ---------------- | ------------- | ------------- | + > | | initial alpha | subsequent alpha | beta release | final release | patch release | + > | `RELEASE_VERSION` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.3.0-beta.0` | `v1.3.0` | `v1.3.1` | + > | `START_TAG` | `v1.2.0` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.2.0`\* | `v1.3.0` | + > | `END_REV` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | + > | `BRANCH` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | + > + > \*Do not use a patch here (e.g., no `v1.2.3`). It must be `v1.2.0`: + > you must use the latest tag that belongs to the release branch you are + > releasing on; in the above example, the release branch is + > `release-1.3`, and the latest tag on that branch is `v1.2.0`. + + > **Note:** The 4 variables are described in [the README of the + `release-notes` + tool](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md#options). + For your convenience, the following table summarizes what you need to know: + > + > | Variable | Description | + > | ----------------- | --------------------------------------- | + > | `RELEASE_VERSION` | The git tag | + > | `START_TAG`\* | The git tag of the "previous"\* release | + > | `END_REV` | Name of your release branch (inclusive) | + > | `BRANCH` | Name of your release branch | + +3. **(final release only)** Prepare the Website "Upgrade Notes" PR. + + Make sure that a PR with the new upgrade + document is ready to be merged on + [cert-manager/website](https://github.com/cert-manager/website). See for + example, see + [upgrading-1.0-1.1](https://cert-manager.io/docs/installation/upgrading/upgrading-1.0-1.1/). + +4. **(final + patch releases)** Prepare the Website "Release Notes" PR. + + **⚠️ This step can be done ahead of time.** + + The steps below need to happen using `master` (**final release**) or + `release-1.x` (**patch release**). The PR will be merged after the release. + + 1. Go to the Generate `release-notes.md` using the instructions further below + (Ctrl+F and look for `github-release-description.md`). + 2. Remove the "Dependencies" section. + 3. Edit any `release-note` block in the PR description that doesn't follow + the [release-note guidelines](../contributing/contributing-flow.md#release-note-guidelines) + and copy the same change into `release-notes.md` (or re-generate the + file). + 4. Add the section "Major themes" and "Community" by taking example on the + previous release note pages. + 5. Replace the GitHub issue numbers and GitHub handles (e.g., `#1234` or + `@maelvls`) with actual links using the following command: + + ```bash + sed github-release-description.md \ + -e 's$#([0-9]+)$[#\1](https://github.com/cert-manager/cert-manager/pull/\1)$g' \ + -e 's$@(\w+)$[@\1](https://github.com/\1)$g' \ + -E \ + -i + ``` + + 6. Move `release-notes.md` to the website repo: + + ```bash + # From the cert-manager repo. + mv release-notes.md ../website/content/docs/release-notes-1.X.md + ``` + + 7. Add an entry to `content/docs/manifest.json`: + + ```diff + { + "title": "Release Notes", + "routes": [ + + { + + "title": "v1.12", + + "path": "/docs/release-notes/release-notes-1.12.md" + + }, + ``` + + 8. Add a line to the file `content/docs/release-notes/README.md`. + +5. **(final + patch release)** Prepare the Website "Bump Versions" PR. + + **⚠️ This step can be done ahead of time.** + + In that PR: + + 1. (**final release**) Update the section "Supported releases" in the + [supported-releases](../installation/supported-releases.md) page. + 2. (**final release**) Update the section "How we determine supported + Kubernetes versions" on the + [supported-releases](../installation/supported-releases.md) page. + 3. (**final release**) Bump the version that appears in + `scripts/gendocs/generate-new-import-path-docs`. For example: + + ```diff + -LATEST_VERSION="v1.11-docs" + +LATEST_VERSION="v1.12-docs" + + -genversionwithcli "release-1.11" "$LATEST_VERSION" + +genversionwithcli "release-1.12" "$LATEST_VERSION" + ``` + + 4. (**final + patch release**) Bump all versions present in installation + instructions. To find these versions: + + ```bash + find ./content/docs/installation -name '*.md' -not -path '*/upgrad**' -exec sed -i.bak 's/1.11../1.12.0/g' '{}' \; + rm -f **/*.bak + ``` + + To check that all mentions of that version are gone, run: + + ```bash + grep -R -n -F 'v1.11.' content/docs/installation + ``` + + 5. (**final release only**) Freeze the `docs/` folder by creating a copy , + removing the pages from that copy that don't make sense to be versioned, + and updating the `manifest.json` file: + + ```bash + cp -r content/docs content/v1.12-docs + rm -rf content/v1.12-docs/{installation/supported-releases,installation/upgrading,release-notes} + sed -i.bak 's|docs|v1.12-docs|g' content/v1.12-docs/manifest.json + cat content/v1.12-docs/manifest.json \ + | jq 'del(.. | select(.path? | select(.) | test(".*(installation/supported-releases.md|installation/upgrading|release-notes).*")))' \ + | jq 'del(.. | select(.routes? == []))' >/tmp/manifest \ + && mv /tmp/manifest content/v1.12-docs/manifest.json + ``` + + 6. (**final + patch releases**) Update the [API docs](https://cert-manager.io/docs/reference/api-docs/) and [CLI docs](https://cert-manager.io/docs/cli//): + + ```bash + # From the website repository, on the master branch. + ./scripts/gendocs/generate + ``` + +6. Check that the `origin` remote is correct. To do that, run the following + command and make sure it returns the upstream + `https://github.com/cert-manager/cert-manager.git`: + + ```bash + # Must be run from the cert-manager repo folder. + git remote -v | grep origin + ``` + + It should show: + + ```text + origin https://github.com/jetstack/cert-manager (fetch) + origin https://github.com/jetstack/cert-manager (push) + ``` + +7. Place yourself on the correct branch: + + - **(initial alpha and subsequent alpha)**: place yourself on the `master` + branch: + + ```bash + git checkout master + git pull origin master + ``` + + - **(initial beta only)** The release branch doesn't exist yet, so let's + create it and push it: + + ```bash + # Must be run from the cert-manager repo folder. + git checkout master + git pull origin master + git checkout -b release-1.12 master + git push origin release-1.12 + ``` + + **GitHub permissions**: `git push` will only work if you have the `admin` + GitHub permission on the cert-manager repo to create or push to the + branch, see [prerequisites](#prerequisites). If you do not have this + permission, you will have to open a PR to merge master into the release + branch), and wait for the PR checks to become green. + + - **(subsequent beta, patch release and final release)**: place yourself on + the release branch: + + ```bash + git checkout release-1.12 + git pull origin release-1.12 + ``` + + You don't need to fast-forward the branch because things have been merged + using `/cherry-pick release-1.0`. + + **Note about the code freeze:** + + The first beta starts a new "code freeze" period that lasts until the + final release. Just before the code freeze, we fast-forward everything + from master into the release branch. + + During the code freeze, we continue merging PRs into master as usual. + + We don't fast-forward master into the release branch for the second (and + subsequent) beta, and only `/cherry-pick release-1.0` the fixes that should be part + of the subsequent beta. + + We don't fast-forward for patch releases and final releases; instead, we + prepare these releases using the `/cherry-pick release-1.0` command. + + > Note about branch protection: The release branches are protected by [GitHub branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule), which is [configured automatically by Prow](https://github.com/cert-manager/testing/blob/500b990ad1278982b10d57bf8fbca383040d2fe8/config/config.yaml#L27-L36). + > This prevents anyone *accidentally* pushing changes directly to these branches, even repository administrators. + > If you need, for some reason, to fast forward the release branch, + > you should delete the branch protection for that release branch, using the [GitHub branch protection web UI](https://github.com/cert-manager/cert-manager/settings/branches). + > This is only a temporary change to allow you to update the branch. + > [Prow will re-apply the branch protection within 24 hours](https://docs.prow.k8s.io/docs/components/optional/branchprotector/#updating). + +8. Create the required tags for the new release locally and push it upstream (starting the cert-manager build): + + ```bash + echo $RELEASE_VERSION + git tag -m"$RELEASE_VERSION" $RELEASE_VERSION + # be sure to push the named tag explicitly; you don't want to push any other local tags! + git push origin $RELEASE_VERSION + ``` + + > **Note**: `git push` will only work if you have the `admin` GitHub + > permission on the cert-manager repo to create or push to the branch, see + > [prerequisites](#prerequisites). If you do not have this permission, you + > will have to open a PR to merge master into the release branch), and + > wait for the PR checks to become green. + + > **Note 2:** For recent versions of cert-manager, the tag being pushed will trigger a Google Cloud Build job to start, + > kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to + > the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). + +9. In this step, we make sure the Go module + `github.com/cert-manager/cert-manager/cmd/cmctl` can be imported by + third-parties. + + First, create a temporary branch. + + ```bash + # Must be run from the cert-manager repo folder. + git checkout -b "update-cmd/ctl/$RELEASE_VERSION" + ``` + + Second, update the `cmd/cmctl`'s `go.mod` with the tag we just created: + + ```bash + # Must be run from the cert-manager repo folder. + cd cmd/cmctl + go get github.com/cert-manager/cert-manager@$RELEASE_VERSION + cd ../.. + + find . -name go.mod -not -path ./_bin/\* -exec dirname '{}' \; | xargs -L1 -I@ sh -c 'cd @; go mod tidy' + git add **/go.mod **/go.sum + git commit -m"Update cmd/cmctl's go.mod to $RELEASE_VERSION" + ``` + + Third, create a tag for the `cmd/cmctl` module: + + ```bash + # Must be run from the cert-manager repo folder. + git tag -m"cmd/ctl/$RELEASE_VERSION" "cmd/ctl/$RELEASE_VERSION" + git push origin "cmd/ctl/$RELEASE_VERSION" + ``` + + > **Note:** the reason we need to do this is explained on Stack Overflow: + [how-are-versions-of-a-sub-module-managed][] + + [how-are-versions-of-a-sub-module-managed]: https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 + + Then, open a PR to merge that change and go back to the release branch + with the following commands: + + ```bash + gh pr create \ + --title "[Release $RELEASE_VERSION] Update cmd/cmctl's go.mod to $RELEASE_VERSION" \ + --body-file - --base $BRANCH < **Note:** This step is about creating the description that will be + > copy-pasted into the GitHub release page. The creation of the "Release + > Note" page on the website is done in a previous step. + + 1. Check that all the 4 environment variables are ready: + + ```bash + echo $RELEASE_VERSION + echo $START_TAG + echo $END_REV + echo $BRANCH + ``` + + 2. Generate `github-release-description.md` with the following command: + + ```bash + # Must be run from the cert-manager folder. + export GITHUB_TOKEN=*your-token* + git fetch origin $BRANCH + export START_SHA="$(git rev-list --reverse --ancestry-path $(git merge-base $START_TAG $BRANCH)..$BRANCH | head -1)" + release-notes --debug --repo-path cert-manager \ + --org cert-manager --repo cert-manager \ + --required-author "jetstack-bot" \ + --output github-release-description.md + ``` + +

        + The GitHub token **does not need any scope**. The token is required + only to avoid rate-limits imposed on anonymous API users. +

        + 3. Add a one-sentence summary at the top. + + 4. **(final release only)** Write the "Community" section, following the example of past releases such as [v1.12.0](https://github.com/cert-manager/cert-manager/releases/tag/v1.12.0). If there are any users who didn't make code contributions but helped in other ways (testing, PR discussion, etc), be sure to thank them here! + +11. Check that the build is complete and send Slack messages about the release: + + 1. For recent versions of cert-manager, the build will have been automatically + triggered by the tag being pushed earlier. You can check if it's complete on + the [GCB Build History](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). + + 2. If you're releasing an older version of cert-manager (earlier than 1.10) then the automatic build + will failed because the GCB config for that build wasn't backported. + In this case, you'll need to trigger the build manually using `cmrel`, which takes about 5 minutes: + + ```bash + # Must be run from the "cert-manager/release" repo folder. + cmrel makestage --ref=$RELEASE_VERSION + ``` + + This build takes ~5 minutes. It will build all container images and create + all the manifest files, sign Helm charts and upload everything to a storage + bucket on Google Cloud. These artifacts will then be published and released + in the next steps. + + 3. In any case, send a first Slack message to `#cert-manager-dev`: + +

        + Releasing 1.2.0-alpha.2 🧵 +

        + +

        + 🔰 Please have a quick look at the build log as it might contain some unredacted + data that we forgot to hide. We try to make sure the sensitive data is + properly redacted but sometimes we forget to update this. +

        + + 4. Send a second Slack message in reply to this first message with the + Cloud Build job link. For example, the message might look like: + +

        + Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237 +

        + +12. Run `cmrel publish`: + + 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are + valid. Run the following command: + + ```bash + # Must be run from the "cert-manager/release" repo folder. + cmrel publish --release-name "$RELEASE_VERSION" + ``` + + You can view the progress by clicking the Google Cloud Build URL in the + output of this command. + + 2. While the build is running, send a third Slack message in reply to the first message: + +

        + Follow the `cmrel publish` dry-run build: https://console.cloud.google.com/cloud-build/builds16f6f875-0a23-4fff-b24d-3de0af207463?project=1021342095237 +

        + + 3. Now publish the release artifacts for real. The following command will publish the artifacts to GitHub, `Quay.io` and to our + [helm chart repository](https://charts.jetstack.io): + + ```bash + # Must be run from the "cert-manager/release" repo folder. + cmrel publish --nomock --release-name "$RELEASE_VERSION" + ``` + +
        + ⏰ Upon completion there will be: +
          +
        1. + A draft release of cert-manager on GitHub +
        2. +
        3. + A pull request containing the new Helm chart +
        4. +
        +
        + + 4. While the build is running, send a fourth Slack message in reply to the first message: + +

        + Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237 +

        + +13. Publish the GitHub release: + + 1. Visit the draft GitHub release and paste in the release notes that you + generated earlier. You will need to manually edit the content to match + the style of earlier releases. In particular, remember to remove + package-related changes. + + 2. **(initial alpha, subsequent alpha and beta only)** Tick the box "This is + a pre-release". + + 3. **(final release and patch release)** Tick the box "Set as the latest + release". + + 4. Click "Publish" to make the GitHub release live. + +14. Merge the pull request containing the Helm chart: + + The Helm charts for cert-manager are served using Cloudflare pages + and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). + The `cmrel publish --nomock` step (above) will have created a PR in this repository which you now have to review and merge, as follows: + + 1. [Visit the pull request](https://github.com/jetstack/jetstack-charts/pulls) + 2. Review the changes + 3. Fix any failing checks + 4. Merge the PR + 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). + +15. **(final + patch releases)** Merge the 4 Website PRs: + + 1. Merge the PRs "Release Notes", "Upgrade Notes", and "Freeze And Bump + Versions" that you have created previously. + 2. Create the PR "Merge release-next into master" by [clicking + here][ff-release-next]. + + If you see the label `dco-signoff: no`, add a comment on the PR with: + + ```text + /override dco + ``` + + This command is necessary because some the merge commits have been + written by the bot and do not have a DCO signoff. + + [ff-release-next]: https://github.com/cert-manager/website/compare/master...release-next?quick_pull=1&title=%5BPost-Release%5D+Merge+release-next+into+master&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco + +16. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. + + Assuming you have `brew` installed, you can use the `brew bump-formula-pr` + command to do this. You'll need the new tag name and the commit hash of that + tag. See `brew bump-formula-pr --help` for up to date details, but the command + will be of the form: + + ```bash + brew bump-formula-pr --dry-run --tag v0.10.0 --revision da3265115bfd8be5780801cc6105fa857ef71965 cmctl + ``` + + Replacing the tag and revision with the new ones. + + This will take time for the Homebrew team to review. Once the pull reqeust + against https://github.com/homebrew/homebrew-core has been opened, continue + with further release steps. + +17. Post a Slack message as an answer to the first message. Toggle the check + box "Also send to `#cert-manager-dev`" so that the message is well + visible. Also cross-post the message on `#cert-manager`. + +

        + https://github.com/cert-manager/cert-manager/releases/tag/v1.0.0 🎉 +

        + +18. **(final release only)** Show the release to the world: + + 1. Send an email to + [`cert-manager-dev@googlegroups.com`](https://groups.google.com/g/cert-manager-dev) + with the `release` label + ([examples](https://groups.google.com/g/cert-manager-dev?label=release)). + + 2. Send a tweet on the cert-manager Twitter account! Login details are in Jetstack's 1password (for now). + ([Example tweet](https://twitter.com/CertManager/status/1612886311957831680)). Make sure [@JetstackHQ](https://twitter.com/JetstackHQ) retweets it! + + 3. Send a toot from the cert-manager Mastodon account! Login details are in Jetstack's 1password (for now). + ([Example toot](https://infosec.exchange/@CertManager/109666434738850493)) + +19. Proceed to the post-release "testing and release" steps: + + 1. **(initial beta only)** Create a PR on + [cert-manager/release](https://github.com/cert-manager/release) in order to + add the new release to our list of periodic ProwJobs. Use [this PR](https://github.com/cert-manager/testing/pull/774/) as an example. + + 2. **(initial beta only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and + open a PR to [cert-manager/testing](https://github.com/cert-manager/testing) adding the generated prow configs. + Use [this PR](https://github.com/cert-manager/testing/pull/766) as an example. + + 3. **(final release only)** Create a PR on + [cert-manager/release](https://github.com/cert-manager/release), + removing the now unsupported release version (2 versions back) in this file: + + ```plain + prowspecs/specs.go + ``` + + This will remove the periodic ProwJobs for this version as they're no longer needed. + + 4. **(final release only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and + open a PR to [jetstack/testing](https://github.com/cert-manager/testing) adding the generated prow configs. + + 5. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/cert-manager/testing) + and update the [milestone_applier](https://github.com/cert-manager/testing/blob/3110b68e082c3625bf0d26265be2d29e41da14b2/config/plugins.yaml#L69) + config so that newly raised PRs on master are applied to a new milestone + for the next release. E.g. if master currently points at the `v1.10` milestone, change it to point at `v1.11`. + + If the [milestone](https://github.com/cert-manager/cert-manager/milestones) for the next release doesn't exist, + create it first. If you consider the milestone for the version you just released to be complete, close it. + + 6. Open a PR against the Krew index such as [this one](https://github.com/kubernetes-sigs/krew-index/pull/1724), + bumping the versions of our kubectl plugins. This is likely only worthwhile if + cmctl / kubectl plugin functionality has changed significantly or after the first release of a new major version. + + 7. Create a new OLM package and publish to OperatorHub + + cert-manager can be [installed](https://cert-manager.io/docs/installation/operator-lifecycle-manager/) using Operator Lifecycle Manager (OLM) + so we need to create OLM packages for each cert-manager version and publish them to both + [`operatorhub.io`](https://operatorhub.io/operator/cert-manager) and the equivalent package index for RedHat OpenShift. + + Follow [the cert-manager OLM release process](https://github.com/cert-manager/cert-manager-olm#release-process) and, once published, + [verify that the cert-manager OLM installation instructions](https://cert-manager.io/docs/installation/operator-lifecycle-manager/) still work. + +## Older Releases + +The above guide only applies for versions of cert-manager from v1.8 and newer. + +Older versions were built using Bazel and this difference in build process is reflected in the release process. + +### cert-manager 1.6 and 1.7 + +Follow [this older version][older-release-process] of the release process on GitHub, rather than the guide on this website. + +The most notable difference is you'll call `cmrel stage` rather than `cmrel makestage`. You should be fine to use the latest +version of `cmrel` to do the release. + +### cert-manager 1.5 and earlier + +If you're releasing version 1.5 or earlier you must also be sure to install a different version of `cmrel`. + +In the step where you install `cmrel`, you'll want to run the following instead: + +```bash +go install github.com/cert-manager/release/cmd/cmrel@cert-manager-pre-1.6 +``` + +This will ensure that the version of `cmrel` you're using is compatible with the version of cert-manager you're releasing. + +In addition, when you check out the `cert-manager/release` repository you should be sure to check out the `cert-manager-pre-1.6` tag in that repo: + +```bash +git checkout cert-manager-pre-1.6 +``` + +Other than the different `cert-manager/release` tag and `cmrel` version, you can follow the [same older release documentation][older-release-process] as +is used for 1.6 and 1.7 - just remember to change the version of `cmrel` you install! + +[older-release-process]: https://github.com/cert-manager/website/blob/6fa0db74de0ae17d7be638a08155d1b4e036aaa9/content/en/docs/contributing/release-process.md?plain=1 diff --git a/content/v1.13-docs/contributing/security.md b/content/v1.13-docs/contributing/security.md new file mode 100644 index 0000000000..6d33165ede --- /dev/null +++ b/content/v1.13-docs/contributing/security.md @@ -0,0 +1,13 @@ +--- +title: Reporting Security Issues +description: 'cert-manager contributing: Security policy' +--- + +Security is the number one priority for cert-manager. If you think you've +found a vulnerability in any cert-manager project, please follow the +[vulnerability reporting process](https://github.com/cert-manager/cert-manager/blob/master/SECURITY.md) +documented in the main cert-manager repository. + +The reporting process is the same for all repositories under the +cert-manager organization. The process is documented in one place to ensure +a single source of truth and a single list of [security contacts](https://github.com/cert-manager/cert-manager/blob/master/SECURITY_CONTACTS.md). \ No newline at end of file diff --git a/content/v1.13-docs/contributing/sign-off.md b/content/v1.13-docs/contributing/sign-off.md new file mode 100644 index 0000000000..2e0dd8cbfa --- /dev/null +++ b/content/v1.13-docs/contributing/sign-off.md @@ -0,0 +1,77 @@ +--- +title: DCO Sign Off +description: 'cert-manager contributing: DCO Sign-off' +--- + +All contributors to the project retain copyright to their work, but must only submit +work which they have the rights to submit. + +We require all contributors to acknowledge that they have the rights to the code they're contributing +by signing their commits in git using a "DCO Sign Off". Note that this is different to "commit signing" +using something like PGP or [`gitsign`](https://github.com/sigstore/gitsign)! + +Any copyright notices in a cert-manager repo should specify the authors as +"The cert-manager Authors". + +To sign your work, pass the `--signoff` option to `git commit` or `git rebase`: + +```bash +# Sign off a commit as you're making it +git commit --signoff -m"my commit" + +# Add a signoff to the last commit you made +git commit --amend --signoff + +# Rebase your branch against master and sign off every commit in your branch +git rebase --signoff master +``` + +This will add a line similar to the following at the end of your commit: + +```text +Signed-off-by: Joe Bloggs +``` + +By signing off a commit you're stating that you certify the following: + +```text +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +That statement is taken from [https://developercertificate.org/](https://developercertificate.org/). diff --git a/content/v1.13-docs/contributing/signing-keys.md b/content/v1.13-docs/contributing/signing-keys.md new file mode 100644 index 0000000000..74d5382d86 --- /dev/null +++ b/content/v1.13-docs/contributing/signing-keys.md @@ -0,0 +1,72 @@ +--- +title: Signing Keys +description: 'cert-manager contributing: Code signing / Signing keys' +--- + +This page describes the bootstrapping process for a key, including how to do it and why a bootstrapping +process is required. + +## What do we Serve? + +To facilitate verification of signatures, we serve public key information from the cert-manager website +directly. It's important to serve the keys from a different location to where the artifacts are hosted; if the +keys were hosted at the same location as the artifacts, an attacker able to change the artifacts would be able +to also change the keys! + +We serve several key types under `static/public-keys`: + +- `cert-manager-pgp-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.asc`: ASCII-armored PGP public key, used for verifying signatures on helm charts via `helm verify` (after being converted to a keyring) +- `cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg`: Old style GPG keyring, needed by the `--keyring` parameter to `helm verify`. See Keyring below. +- `cert-manager-pubkey-2021-09-20.pem`: The raw, PEM-encoded public key used for signing. Cannot be used with GPG (and therefore helm), but should be used for other verification types. + +## Background / Architecture + +Code signing for cert-manager artifacts is done entirely using cloud KMS keys, to ensure that nobody +can get access to the private keys in plain-text; all signing operations using the key are therefore +done through cloud APIs and are logged. + +Currently, all keys are on Google KMS, since the rest of cert-manager's release infrastructure is also +in GCP. The key - and the role bindings which allow access to it - are specified in terraform in a closed +source Jetstack repo. + +## Why Bootstrap? + +While the private key is not retrievable for a KMS key, the public key is and _must_ be retrieved so that +end-users can verify signatures made by the key. In GCP, retrieving the public key is itself an +[API call](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys.cryptoKeyVersions/getPublicKey) +which returns the raw key in a PEM encoded format. + +That PEM-encoded public key works for some cases (e.g. verifying container signature made using `cosign`) but +it's not sufficient for Helm chart verification, since Helm chart signing (sadly) requires the use of PGP. + +## Bootstrapping a PGP Identity + +It's possible to use a shim to use GCP KMS as a PGP key which enables us to avoid having two separate signing keys, +but PGP public identities are slightly more complicated than plain public keys; they also contain a name, +creation time, comment and email address to identify the signer. This public "identity" must itself be signed by the +private key (to prove that the information in the identity is legitimate). + +This bootstrapping can be done using the cert-manager release tool, `cmrel`: + +```console +# note that the key name might not exactly match this in the future +$ cmrel bootstrap-pgp --key "projects/cert-manager-release/locations/europe-west1/keyRings/cert-manager-release/cryptoKeys/cert-manager-release-signing-key/cryptoKeyVersions/1" +``` + +This will trigger a cloud build job which will output both the armored PGP identity and the raw PEM public key; the values +can be copied from the job output. + +### GPG Keyring + +As an additional UX feature, we can also generate a GPG keyring from the PGP identity, since the keyring is what's required +by the Helm CLI to actually validate a chart: + +```console +# Example of verifying a chart. +$ helm verify --keyring cert_manager_keyring_1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg /path/to/chart.tgz +Signed by: cert-manager Maintainers +Using Key With Fingerprint: 1020.... +Chart Hash Verified: sha256:bb86... +``` + +The keyring can be generated using [this script](https://github.com/cert-manager/release/blob/a219e18b2e64ef078bf73b3641d589b43d1fccb8/hack/helm_keyring.sh). \ No newline at end of file diff --git a/content/v1.13-docs/contributing/third-party-code-donation.md b/content/v1.13-docs/contributing/third-party-code-donation.md new file mode 100644 index 0000000000..f53da8e3c6 --- /dev/null +++ b/content/v1.13-docs/contributing/third-party-code-donation.md @@ -0,0 +1,79 @@ +--- +title: Donating Third Party Code to cert-manager +description: 'cert-manager contributing: Third party code donations' +--- + +The cert-manager project welcomes external contributions and has benefited greatly from thousands +of commits from hundreds of different contributors. Most code is usually committed through pull +requests to a specific repo, whether that be the main cert-manager repository or one of the associated +repositories such as the website. + +Some contributions aren't as well suited to that kind of workflow, however. That would most likely +be because their functionality doesn't belong in any particular existing cert-manager repo, while still +relating to the cert-manager project. + +This document aims to address the donation of code to the cert-manager project, and to provide a +framework for sustainable contributions which can be tested and relied upon going forwards by both +cert-manager maintainers and users. + +The requirements in this document are based in part on what's done for CoreDNS, Envoy, Kubernetes +and containerd. + +## Requirements + +1. Code must be licensed appropriately, including any dependencies + We'd prefer [Apache 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) since that's + what cert-manager [uses](https://github.com/cert-manager/cert-manager/blob/master/LICENSE), but the + license must be [OSI approved](https://opensource.org/licenses). +2. Code must conform to CNCF standards and due diligence requirements + You don't need to go over this with a fine-toothed comb; the intent here is that no code donation + should have a negative effect on cert-manager's progress as a CNCF project. See the + [CNCF due diligence template](https://github.com/cncf/toc/blob/main/process/dd-review-template.md) +3. Must be sponsored by an existing maintainer + An existing regular contributor to cert-manager must sponsor the adoption of any third party code + donation. This ensures that there's a single point of contact for the party donating the code. +4. Must pass cert-manager conformance tests + This might not apply to all donations, but where conformance tests exist any donated code must + pass them. E.g. for [external issuers](https://github.com/cert-manager/cert-manager/blob/dffbf391dbb0fc6c1cfea62e561a9c6f54362ab0/test/e2e/suite/conformance/certificates/external/external.go#L41-L62) +5. Must provide a point-of-contact for questions about the project for at least 3 months after acceptance + We don't anticipate that we'd need to reach out often after the donation has been accepted, + but it's important to have someone we can reach out to if we need to. +6. The donation must be a defined extension type or justify why it doesn't belong in the main repositories + E.g. an ACME DNS solver, a custom issuer or an ACME HTTP solver +7. Code must have a similar level of quality to cert-manager itself + This could be enforced by, for example, running static analysis tools on the code base similar to + those used by cert-manager. +8. Code must have a non-trivial test suite, including both unit tests and end-to-end tests + These tests must be able to be run in their entirety after a PR is raised against the repo. We don't + need 100% code coverage, but there should be tests for important functionality. +9. The project must adopt the cert-manager security policy and link back to the policy, as in e.g. + the [istio-csr `SECURITY.md`](https://github.com/cert-manager/istio-csr/blob/master/SECURITY.md) +10. Must have DCO sign-offs or coverage for all commits + To ensure that all code can legally be donated, all commits should have DCO sign-off or else have + a positive affirmation made by each contributor prior to donation. See below. + +## Preferences + +These items are not absolutely necessary but they definitely help if a code donation is to be accepted. + +- Should be written in Go + We don't _need_ code to be written in Go, but we'd much prefer that it is. Since cert-manager itself + is written in Go, code donations in Go allow us to use existing experience and tooling on Go code. + +## DCO Signoff + +As a method of ensuring that the donator has permission to donate the code, we require DCO sign-offs - +or something equivalent - to be in place at the time of the donation. + +The cert-manager [DCO signoff process](https://cert-manager.io/docs/contributing/sign-off/) +would be appropriate. Existing contributors could bootstrap this process by creating an empty signed-off +with a note that previous code should be considered signed off as of that commit: + +```bash +git commit --allow-empty --signoff --message="bootstrapping DCO signoff for past commits" +``` + +## After Donation + +Code files in the donated repository must be updated to include the relevant +[cert-manager boilerplate](https://github.com/cert-manager/cert-manager/blob/master/hack/boilerplate/boilerplate.go.txt) \ No newline at end of file diff --git a/content/v1.13-docs/faq/README.md b/content/v1.13-docs/faq/README.md new file mode 100644 index 0000000000..6f885591a7 --- /dev/null +++ b/content/v1.13-docs/faq/README.md @@ -0,0 +1,180 @@ +--- +title: Frequently Asked Questions (FAQ) +description: Find answers to some frequently asked questions about cert-manager +--- + +On this page you will find answers to some frequently asked questions about cert-manager. + +## Terminology + +### What does `publicly trusted` and `self-signed` mean? + +These terms are defined in the [TLS Terminology page](../reference/tls-terminology.md). + +### What do the terms `root`, `intermediate` and `leaf` _certificate_ mean? + +These terms are defined in the [TLS Terminology page](../reference/tls-terminology.md). + +## Certificates + +### Can I trigger a renewal from cert-manager at will? + +This is a feature in cert-manager starting in `v0.16` using the `cmctl` CLI. More information can be found on [the renew command's page](../reference/cmctl.md#renew) + +### When do certs get re-issued? + +To determine if a certificate needs to be re-issued, cert-manager looks at the the spec of `Certificate` resource and latest `CertificateRequest`s as well as the data in `Secret` containing the X.509 certificate. + +The issuance process will always get triggered if the: + +- `Secret` named on `Certificate`'s spec, does not exist, is missing private key or certificate data or contains corrupt data +- private key stored in the `Secret` does not match the private key spec on `Certificate` +- public key of the issued certificate does not match the private key stored in the `Secret` +- cert-manager issuer annotations on the `Secret` do not match the issuer specified on the `Certificate` +- DNS names, IP addresses, URLS or email addresses on the issued certificate do not match those on the `Certificate` spec +- certificate needs to be renewed (because it has expired or the renewal time is now or in the past) +- certificate has been marked for renewal manually [using `cmctl`](../reference/cmctl.md#renew) + +Additionally, if the latest `CertificateRequest` for the `Certificate` is found, cert-manager will also re-issue if: + +- the common name on the CSR found on the `CertificateRequest` does not match that on the `Certificate` spec +- the subject fields on the CSR found on the `CertificateRequest` do not match the subject fields of the `Certificate` spec +- the duration on the `CertificateRequest` does not match the duration on the `Certificate` spec +- `isCA` field value on the `Certificate` spec does not match that on the `CertificateRequest` +- the DNS names, IP addresses, URLS or email addresses on the `CertificateRequest` spec do not match those on the `Certificate` spec +- key usages on the `CertificateRequest` spec do not match those on the `Certificate` spec + +Note that for certain fields re-issuance on change gets triggered only if there +is a `CertificateRequest` that cert-manager can use to determine whether +`Certificate`'s spec has changed since the previous issuance. This is because +some issuers may not respect the requested values for these fields, so we cannot +rely on the values in the issued X.509 certificates. One such field is +`.spec.duration`- change to this field will only trigger re-issuance if there is +a `CertificateRequest` to compare with. In case where you need to re-issue, but +re-issuance does not get triggered automatically due to there being no +`CertificateRequest` (i.e after backup and restore), you can use [`cmctl +renew`](../reference/cmctl.md#renew) to trigger it manually. + +### Why isn't my root certificate in my issued Secret's `tls.crt`? + +Occasionally, people work with systems which have made a flawed choice regarding TLS chains. The [TLS spec](https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.2) +has the following section for the "Server Certificate" section of the TLS handshake: + +> This is a sequence (chain) of certificates. The sender's +> certificate MUST come first in the list. Each following +> certificate MUST directly certify the one preceding it. Because +> certificate validation requires that root keys be distributed +> independently, the self-signed certificate that specifies the root +> certificate authority MAY be omitted from the chain, under the +> assumption that the remote end must already possess it in order to +> validate it in any case. + +In a standard, secure and correctly configured TLS environment, adding a root certificate to the chain is +almost always unnecessary and wasteful. + +There are two ways that a certificate can be trusted: + +- explicitly, by including it in a trust store. +- through a signature, by following the certificate's chain back up to an explicitly trusted certificate. + +Crucially, root certificates are by definition self-signed and they cannot be validated through a signature. + +As such, if we have a client trying to validate the certificate chain sent by the server, the client must already have the +root before the connection is started. If the client already has the root, there was no point in it being sent by the server! + +The same logic with not sending root certificates applies for servers trying to validate client certificates; +the [same justification](https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.6) is given in the TLS RFC. + +### How can I see all the historic events related to a certificate object? + +cert-manager publishes all events to the Kubernetes events mechanism, you can get the events for your specific resources using `kubectl describe `. + +Due to the nature of the Kubernetes event mechanism these will be purged after a while. If you're using a dedicated logging system it might be able or is already also storing Kubernetes events. + +{/* This empty link preserves old links to #what-happens-if-a-renewal-doesn't happen?-will-it-be-tried-again-after-some-time?", which matched the old title of this section */} + +### What happens if issuance fails? Will it be retried? + +cert-manager will retry a failed issuance except for a few rare edge cases where +manual intervention is needed. + +We aim to retry after a short delay in case of ephemeral failures such as +network connection errors and with a longer exponentially increasing delay after +'terminal' failures. + +You can observe that latest issuance has terminally failed if the `Certificate` +has `Issuing` condition set to false and has `status.lastFailureTime` set. In +this case the issuance will be retried after an exponentially increasing delay +(1 to 32 hours) by creating a new `CertficateRequest`. You can trigger an +immediate renewal using the [`cmctl renew` +command](../reference/cmctl.md#renew). Terminal failures occur if the issuer +sets the `CertificateRequest` to failed (for example if CA rejected the request +due to a rate limit being reached) or invalid or if the `CertificateRequest` +gets denied by an approver. + +Ephemeral failures result in the same `CertificateRequest` being re-synced after +a short delay (up to 5 minutes). Typically they can only be observed in +cert-manager controller logs. + +If it appears the issuance has got stuck and `cmctl renew` does not work, you +can delete the latest `CertificateRequest`. This is mostly a harmless action +(the worst that could happen is duplicate issuance if there was a potentially +successful one in progress), but we do aim for this to not be part of user flow- +do reach out if you think you have found a case where the flow could be +improved. + +### Is ECC (elliptic-curve cryptography) supported? + +cert-manager supports ECDSA key pairs! You can set your certificate to use ECDSA in the `privateKey` part of your Certificate resource. + +For example: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: ecdsa +spec: + secretName: ecdsa-cert + isCA: false + privateKey: + algorithm: ECDSA + size: 256 + dnsNames: + - ecdsa.example.com + issuerRef: + [...] +``` + +### If `renewBefore` or `duration` is not defined, what will be the default value? + +Default `duration` is [90 days](https://github.com/cert-manager/cert-manager/blob/v1.2.0/pkg/apis/certmanager/v1/const.go#L26). If `renewBefore` has not been set, `Certificate` will be renewed 2/3 through its _actual_ duration. + +## Miscellaneous + +### Kubernetes has a builtin `CertificateSigningRequest` API. Why not use that? + +Kubernetes has a [Certificate Signing Requests API], +and a [`kubectl certificates` command] which allows you to approve certificate signing requests +and have them signed by the certificate authority (CA) of the Kubernetes cluster. + +This API and CLI have occasionally been misused to sign certificates for use by non-control-plane Pods but this is a mistake. +For the security of the Kubernetes cluster, it is important to limit access to the Kubernetes certificate authority, +and it is important that you do not use that certificate authority to sign certificates which are used outside of the control-plane, +because such certificates increase the opportunity for attacks on the Kubernetes API server. + +In Kubernetes 1.19 the [Certificate Signing Requests API] has reached V1 +and it can be used more generally by following (or automating) the [Request Signing Process]. + +cert-manager currently has some [limited experimental support] for this resource. + +[Certificate Signing Requests API]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#certificatesigningrequest-v1-certificates-k8s-io +[`kubectl certificates` command]: https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#certificate +[Request signing process]: https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#request-signing-process +[limited experimental support]: ../usage/kube-csr.md + +### How to write "cert-manager" + +cert-manager should always be written in lowercase. Even when it would normally be +capitalized such as in titles or at the start of sentences. A hyphen should always be +used between the words, don't replace it with a space and don't remove it. diff --git a/content/v1.13-docs/getting-started/README.md b/content/v1.13-docs/getting-started/README.md new file mode 100644 index 0000000000..c358360f49 --- /dev/null +++ b/content/v1.13-docs/getting-started/README.md @@ -0,0 +1,36 @@ +--- +title: Getting Started with cert-manager +description: Quick start guides for cert-manager +--- + + NGINX Ingress Controller icon + Let's Encrypt icon 292Jacob, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons + Learn how to deploy cert-manager and how to configure it to get certificates for the **NGINX Ingress controller** from **Let's Encrypt**. + + + + Google Kubernetes Engine icon + Let's Encrypt icon 292Jacob, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons + Learn how to deploy cert-manager on **Google Kubernetes Engine** and how to configure it to get certificates for Ingress, from **Let's Encrypt**. + + + + Azure Kubernetes Services icon + Let's Encrypt icon 292Jacob, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons + Learn how to deploy cert-manager on **Azure Kubernetes Service (AKS)** and how to configure it to get certificates for an HTTPS web server, from **Let's Encrypt**. + diff --git a/content/v1.13-docs/installation/README.md b/content/v1.13-docs/installation/README.md new file mode 100644 index 0000000000..39fcd350f3 --- /dev/null +++ b/content/v1.13-docs/installation/README.md @@ -0,0 +1,41 @@ +--- +title: Installation +description: Learn about the various ways you can install cert-manager and how to choose between them +--- + +Learn about the various ways you can install cert-manager and how to choose between them. + +## Default static install + +> You don't require any tweaking of the cert-manager install parameters. + +The default static configuration can be installed as follows: + +```bash +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml +``` + +📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). + +## Getting started + +> You quickly want to learn how to use cert-manager and what it can be used for. + +📖 **kubectl apply**: For new users we recommend [installing cert-manager using kubectl apply and static manifests](./kubectl.md). + +📖 **helm**: You can [use helm to install cert-manager](./helm.md) and this also allows you to customize the installation if necessary. + +📖 **OperatorHub**: If you have an OpenShift cluster, consider [installing cert-manager via OperatorHub](./operator-lifecycle-manager.md), +which you can do from the OpenShift web console. + +🚧 **cmctl**: Try the [experimental `cmctl x install` command](../reference/cmctl.md#install) to quickly install cert-manager. + +## Continuous deployment + +> You know how to configure your cert-manager setup and want to automate this. + +📖 **helm**: You can use [the cert-manager Helm chart](./helm.md) directly with systems like Flux, ArgoCD and Anthos. + +📖 **helm template**: You can use `helm template` to generate customized cert-manager installation manifests. +See [Output YAML using helm template](./helm.md#output-yaml) for more details. +This templated cert-manager manifest can be piped into your preferred deployment tool. diff --git a/content/v1.13-docs/installation/api-compatibility.md b/content/v1.13-docs/installation/api-compatibility.md new file mode 100644 index 0000000000..e31b96ae29 --- /dev/null +++ b/content/v1.13-docs/installation/api-compatibility.md @@ -0,0 +1,22 @@ +--- +title: API compatibility +description: cert-manager API compatibility guarantees +--- + +cert-manager aims to abide by the same API compatibility policy as upstream Kubernetes APIs as documented in the [Kubernetes Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/#deprecating-parts-of-the-api). + +This is to ensure a smooth upgrade and downgrade experience for users, i.e to make sure that users' cert-manager custom resources keep functioning in the same way +after an upgrade or downgrade of cert-manager. + +In some cases, we may need to require users to take actions before upgrading or may need to diverge from the API compatibility promise but we'll treat this as an absolute +last resort. In general the main criteria by which we'd determine whether a change is acceptable would be user value. + +For example in the event of a truly critical bug, a fix that breaks the API compatibility promise by changing the default behavior of an API field _might_ be acceptable. As of yet, though, there has never been a need for such a change. + +## Alpha / Beta API Versions + +As in upstream Kubernetes, We don't commit to preserving alpha or beta API versions indefinitely. + +In cert-manager v1.7 [all alpha and beta API versions prior to `v1` were removed](https://github.com/cert-manager/cert-manager/pull/4635). + +NB: The Kubernetes deprecation policy notes that API removal introduces an issue with objects stored at the removed versions. To fix this, we wrote a [custom tool](https://cert-manager.io/docs/installation/upgrading/remove-deprecated-apis/) that users could run once to migrate their resources. diff --git a/content/v1.13-docs/installation/best-practice.md b/content/v1.13-docs/installation/best-practice.md new file mode 100644 index 0000000000..2db8f3b153 --- /dev/null +++ b/content/v1.13-docs/installation/best-practice.md @@ -0,0 +1,138 @@ +--- +title: Best Practice +description: | + Learn about best practices for deploying cert-manager in production, + and how to configure cert-manager to comply with popular security standards + such as those produced by the CIS, NSA, and BSI. +--- + +In this section you will learn how to configure cert-manager to comply with popular security standards such as +the [CIS Kubernetes Benchmark](https://www.cisecurity.org/benchmark/kubernetes/), +the [NSA Kubernetes Hardening Guide](https://media.defense.gov/2022/Aug/29/2003066362/-1/-1/0/CTR_KUBERNETES_HARDENING_GUIDANCE_1.2_20220829.PDF), or +the [BSI Kubernetes Security Recommendations](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Grundschutz/International/bsi_it_gs_comp_2022.pdf?__blob=publicationFile&v=2#page=475). + +And you will learn about best practices for deploying cert-manager in production; +such as those enforced by tools like [Datree and its built in rules](https://hub.datree.io/built-in-rules), +and those documented by the likes of [Learnk8s in their "Kubernetes production best practices" checklist](https://learnk8s.io/production-best-practices/). + +## Overview + +The default cert-manager resources in the Helm chart or YAML manifests (Deployment, Pod, ServiceAccount etc) +are designed for backwards compatibility rather than for best practice or maximum security. +You may find that the default resources do not comply with the security policy on your Kubernetes cluster +and in that case you can modify the installation configuration using Helm chart values to override the defaults. + +## Use Liveness Probes + +An example of this recommendation is found in the Datree Documentation: +[Ensure each container has a configured liveness probe](https://hub.datree.io/built-in-rules/ensure-liveness-probe): +> Liveness probes allow Kubernetes to determine when a pod should be replaced. +> They are fundamental in configuring a resilient cluster architecture. + +The cert-manager webhook and controller Pods do have liveness probes, +but only the webhook liveness probe is enabled by default. +The cainjector Pod does not have a liveness probe, yet. +More information below. + +### webhook + +The [cert-manager webhook](../concepts/webhook.md) has a [liveness probe which is enabled by default](https://github.com/cert-manager/cert-manager/blob/eafe0d0aae4b7a9411825424f6b43fb623e1ba65/deploy/charts/cert-manager/templates/webhook-deployment.yaml#L108C1-L121) +and the [timings and thresholds can be configured using Helm values](https://github.com/cert-manager/cert-manager/blob/eafe0d0aae4b7a9411825424f6b43fb623e1ba65/deploy/charts/cert-manager/README.template.md?plain=1#L181-L185). + +### controller + +> ℹ️ The cert-manager controller liveness probe was introduced in cert-manager release `1.12`. + +The cert-manager controller has a liveness probe, but it is **disabled by default**. +You can enable it using the Helm chart value `livenessProbe.enabled=true`, +but first read the background information below. + +> 📢 The controller liveness probe is a new feature in cert-manager release 1.12 +> and it is disabled by default, as a precaution, in case it causes problems in the field. +> [Please get in touch](../contributing/README.md) +> and tell is if you have enabled the controller liveness probe in production +> and tell us whether you would like it to be turned on by default. +> Tell us about any circumstances where the controller has become stuck +> and where the liveness probe has been necessary to automatically restart the process. + +The liveness probe for the cert-manager controller is an HTTP probe which connects +to the `/livez` endpoint of a healthz server which listens on port 9443 and runs in its own thread. +The `/livez` endpoint currently reports the combined status of the following sub-systems +and each sub-system has its own `/livez` endpoint. These are: + +* `/livez/leaderElection`: Returns an error if the leader election record has not been renewed + or if the leader election thread has exited without also crashing the parent process. + +> ℹ️ In future more sub-systems could be checked by the `/livez` endpoint, +> similar to how Kubernetes [ensure logging is not blocked](https://github.com/kubernetes/kubernetes/pull/64946) +> and have [health checks for each controller](https://github.com/kubernetes/kubernetes/pull/104667). +> +> 📖 Read about [how to access individual health checks and verbose status information](https://kubernetes.io/docs/reference/using-api/health-checks/) (cert-manager uses the same healthz endpoint multiplexer as Kubernetes). + +### cainjector + +The cainjector Pod does not have a liveness probe or a `/livez` healthz endpoint, +but there is justification for it in the GitHub issue: +[cainjector in a zombie state after attempting to shut down](https://github.com/cert-manager/cert-manager/issues/5889). +Please add your remarks to that issue if you have also experienced this specific problem, +and add your remarks to [Helm: Allow configuration of readiness, liveness and startup probes for all created Pods](https://github.com/cert-manager/cert-manager/issues/5626) if you have a general request for a liveness probe in cainjector. + +### Background Information + +The cert-manager `controller` process and the `cainjector` process, +both use the Kubernetes [leader election library](https://pkg.go.dev/k8s.io/client-go/tools/leaderelection), +to ensure that only one replica of each process can be active at any one time. +The Kubernetes control-plane components also use this library. + +The leader election code runs in a loop in a separate thread (go routine). +If it initially wins the leader election race and if it later fails to renew its leader election lease, it exits. +If the leader election thread exits, all the other threads are gracefully shutdown and then the process exits. +Similarly, if any of the other main threads exit unexpectedly, +that will trigger the orderly shutdown of the remaining threads and the process will exit. + +This adheres to the principle that [Containers should crash when there's a fatal error](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-revisited-how-to-avoid-shooting-yourself-in-the-other-foot/#letitcrash). +Kubernetes will restart the crashed container, and if it crashes repeatedly, +there will be increasing time delays between successive restarts. + +For this reason, the liveness probe should only be needed if there is a bug in this orderly shutdown process, +or if there is a bug in one of the other threads which causes the process to deadlock and not shutdown. + +You may want to enable the liveness probe anyway, for defense against unforeseen bugs and deadlocks, +but you will need to monitor the processes closely and, +tweak the [various liveness probe time settings and thresholds](https://github.com/cert-manager/cert-manager/blob/eafe0d0aae4b7a9411825424f6b43fb623e1ba65/deploy/charts/cert-manager/values.yaml#L254-L268), if necessary. + +> 📖 Read [Configure Liveness, Readiness and Startup Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#before-you-begin) in the Kubernetes documentation, paying particular attention to the notes and cautions in that document. +> +> 📖 Read [Shooting Yourself in the Foot with Liveness Probes](https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-how-to-avoid-shooting-yourself-in-the-foot/#shootingyourselfinthefootwithlivenessprobes) for more cautionary information about liveness probes. + +## Restrict Auto-Mount of Service Account Tokens + +This recommendation is described in the [Kyverno Policy Catalogue](https://kyverno.io/policies/other/res/restrict-automount-sa-token/restrict-automount-sa-token/) as follows: +> Kubernetes automatically mounts ServiceAccount credentials in each Pod. The +> ServiceAccount may be assigned roles allowing Pods to access API resources. +> Blocking this ability is an extension of the least privilege best practice and +> should be followed if Pods do not need to speak to the API server to function. +> This policy ensures that mounting of these ServiceAccount tokens is blocked + +The cert-manager components *do* need to speak to the API server but we still recommend setting `automountServiceAccountToken: false` for the following reasons: +1. Setting `automountServiceAccountToken: false` will allow cert-manager to be installed on clusters where Kyverno (or some other policy system) is configured to deny Pods that have this field set to `true`. The Kubernetes default value is `true`. +2. With `automountServiceAccountToken: true`, *all* the containers in the Pod will mount the ServiceAccount token, including side-car and init containers that might have been injected into the cert-manager Pod resources by Kubernetes admission controllers. + The principle of least privilege suggests that it is better to explicitly mount the ServiceAccount token into the cert-manager containers. + +So it is recommended to set `automountServiceAccountToken: false` and manually add a projected `Volume` to each of the cert-manager Deployment resources, containing the ServiceAccount token, CA certificate and namespace files that would normally be [added automatically by the Kubernetes ServiceAccount controller](https://github.com/kubernetes/kubernetes/blob/3992eda8e61725c470fb6141a7fe4e7f9ee31ea5/plugin/pkg/admission/serviceaccount/admission.go#L421-L460), +and to explicitly add a read-only `VolumeMount` to each of the cert-manager containers. + +An example of this configuration is included in the Helm Chart Values file below. + +## Best Practice Helm Chart Values + +Download the following Helm chart values file and supply it to `helm install`, `helm upgrade`, or `helm template` using the `--values` flag: + +🔗 `values.best-practice.yaml` +```yaml file=../../../public/docs/installation/best-practice/values.best-practice.yaml +``` + +## Other + +This list of recommendations is a work-in-progress. +If you have other best practice recommendations please [contribute to this page](../contributing/contributing-flow.md). diff --git a/content/v1.13-docs/installation/code-signing.md b/content/v1.13-docs/installation/code-signing.md new file mode 100644 index 0000000000..fef64be007 --- /dev/null +++ b/content/v1.13-docs/installation/code-signing.md @@ -0,0 +1,59 @@ +--- +title: cert-manager Signature Verification +description: 'cert-manager installation: Code signing' +--- + +To help prevent [supply chain attacks](https://en.wikipedia.org/wiki/Supply_chain_attack), some cert-manager release +artifacts are cryptographically signed so you can be sure that the version of cert-manager you're about to install +is actually built by and provided by the cert-manager maintainers. + +This signing is vitally important if for any reason you need to use a mirrored version of cert-manager; it allows you +to confirm that the mirror hasn't tampered with the code you're about to install. + +Signing keys required for verification are all available on this website, but the actual key that you need might depend +on the artifact you're trying to validate in the future. At the time of writing, all signing is done using the same underlying +key. + +## Container Images / Cosign + +For all cert-manager versions from `v1.8.0` and later, cert-manager container images are signed and verifiable using [`cosign`](https://docs.sigstore.dev/cosign/overview). + +The simplest way to verify signatures is to download the public key and then pass it to the cosign CLI directly: + +```console +curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem +IMAGE_TAG=v1.13.0 # change as needed +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-controller:$IMAGE_TAG +cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-webhook:$IMAGE_TAG +``` + +For a more fully-featured signature verification process in Kubernetes, check out [`connaisseur`](https://sse-secure-systems.github.io/connaisseur/). + +- PEM-encoded public key: [`cert-manager-pubkey-2021-09-20.pem`](https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem) + +## Helm Charts + +
        +Helm requires the use of PGP for verification; the key format is different. + +Trying to use "plain" PEM encoded public keys during verification will fail. +
        + +For all cert-manager versions from `v1.6.0` and later, Helm charts are signed and verifiable through the Helm CLI. + +The easiest way to verify is to grab the GPG keyring directly, which can then be passed into `helm verify` like so: + +```console +curl -sSL https://cert-manager.io/public-keys/cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg > cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg +helm verify --keyring cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg /path/to/cert-manager-vx.y.z.tgz +``` + +- GPG keyring: [`cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg`](https://cert-manager.io/public-keys/cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg) + +If you know what you're doing and you want the signing key in a format that's easy to import into GPG, +it's available in an ASCII armored version: + +- ASCII-armored signing key: [`cert-manager-pgp-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.asc`](https://cert-manager.io/public-keys/cert-manager-pgp-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.asc) diff --git a/content/v1.13-docs/installation/compatibility.md b/content/v1.13-docs/installation/compatibility.md new file mode 100644 index 0000000000..516bc941a0 --- /dev/null +++ b/content/v1.13-docs/installation/compatibility.md @@ -0,0 +1,114 @@ +--- +title: Compatibility with Kubernetes Platform Providers +description: 'cert-manager installation: Cloud provider compatibility' +--- + +Below you will find details on various compatibility issues and quirks that you +may be affected by when deploying cert-manager. If you believe we've missed something +please feel free to raise an issue or a pull request with the details! + +
        +If you're using AWS Fargate or else if you've specifically configured +cert-manager to run the host's network, be aware that kubelet listens on port +`10250` by default which clashes with the default port for the cert-manager +webhook. + +As such, you'll need to change the webhook's port when setting up cert-manager. + +For installations using Helm, you can set the `webhook.securePort` parameter +when installing cert-manager either using a command line flag or an entry in +your `values.yaml` file. + +If you have a port clash, you could see confusing error messages regarding +untrusted certs. See [#3237](https://github.com/cert-manager/cert-manager/issues/3237) +for more details. +
        + +## GKE + +When Google configure the control plane for private clusters, they automatically +configure VPC peering between your Kubernetes cluster's network and a separate +Google-managed project. + +In order to restrict what Google are able to access within your cluster, the +firewall rules configured restrict access to your Kubernetes pods. This means +that the webhook won't work, and you'll see errors such as +`Internal error occurred: failed calling admission webhook ... the server is +currently unable to handle the request`. + +In order to use the webhook component with a GKE private cluster, you must +configure an additional firewall rule to allow the GKE control plane access to +your webhook pod. + +You can read more information on how to add firewall rules for the GKE control +plane nodes in the [GKE +docs](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules). + + +### GKE Autopilot + +GKE Autopilot mode with Kubernetes < 1.21 does not support cert-manager, +due to a [restriction on mutating admission webhooks](https://github.com/cert-manager/cert-manager/issues/3717). + +As of October 2021, only the "rapid" Autopilot release channel has rolled +out version 1.21 for Kubernetes masters. Installation via the helm chart +may end in an error message but cert-manager is reported to be working by +some users. Feedback and PRs are welcome. + +**Problem**: GKE Autopilot does not allow modifications to the `kube-system`-namespace. + +Historically we've used the `kube-system` namespace to prevent multiple installations of cert-manager in the same cluster. + +Installing cert-manager in these environments with default configuration can cause issues with bootstrapping. +Some signals are: + +* `cert-manager-cainjector` logging errors like: + +```text +E0425 09:04:01.520150 1 leaderelection.go:334] error initially creating leader election record: leases.coordination.k8s.io is forbidden: User "system:serviceaccount:cert-manager:cert-manager-cainjector" cannot create resource "leases" in API group "coordination.k8s.io" in the namespace "kube-system": GKEAutopilot authz: the namespace "kube-system" is managed and the request's verb "create" is denied +``` + +* `cert-manager-startupapicheck` not completing and logging messages like: + +```text +Not ready: the cert-manager webhook CA bundle is not injected yet +``` + +**Solution**: Configure cert-manager to use a different namespace for leader election, like this: + +```console +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version ${CERT_MANAGER_VERSION} --set global.leaderElection.namespace=cert-manager +``` + +The implication for a `kubectl apply` type installation is then either "you must manually update the manifests prior to installation by replacing `kube-system` with cert-manager" or "Don't install cert-manager using kubectl apply. Helm is the recommended solution". + +## AWS EKS + +When using a custom CNI (such as Weave or Calico) on EKS, the webhook cannot be +reached by cert-manager. This happens because the control plane cannot be +configured to run on a custom CNI on EKS, so the CNIs differ between control +plane and worker nodes. + +To address this, the webhook can be run in the host network so it can be reached +by cert-manager, by setting the `webhook.hostNetwork` key to true on your +deployment, or, if using Helm, configuring it in your `values.yaml` file. + +Note that running on the host network will necessitate changing the webhook's +port; see the warning at the top of the page for details. + +### AWS Fargate + +It's worth noting that using AWS Fargate doesn't allow much network configuration and +will cause the webhook's port to clash with the kubelet running on port 10250, as seen +in [#3237](https://github.com/cert-manager/cert-manager/issues/3237). + +When deploying cert-manager on Fargate, you _must_ change the port on which +the webhook listens. See the warning at the top of this page for more details. + +Because Fargate forces you to use its networking, you cannot manually set the networking +type and options such as `webhook.hostNetwork` on the helm chart will cause your +cert-manager deployment to fail in surprising ways. diff --git a/content/v1.13-docs/installation/featureflags.md b/content/v1.13-docs/installation/featureflags.md new file mode 100644 index 0000000000..65ce69a027 --- /dev/null +++ b/content/v1.13-docs/installation/featureflags.md @@ -0,0 +1,72 @@ +--- +title: Feature flags +description: Using feature gated functionality +--- + +New cert-manager features and functionality are often initially implemented behind a feature gate. This is so as to not break users with functionality that has not yet been tested in production as well as to give us a chance to remove or modify API fields and functionality following user feedback. + +We have alpha and beta features. We do not aim to keep any of the features in alpha or beta stage indefinitely. Feature gating should only be used for functionality that can eventually be turned on by default for all users (or, if it is opt-in, can be safely toggled on by any user with a supported cert-manager installation). + +A feature gate can be toggled on/off using `--feature-gates` flags on cert-manager controller. For feature gated functionality that comes with new API fields there is also a corresponding feature gate on webhook that also needs to be enabled using a `--feature-gates` flag if you want to use it. + +**Alpha** + +All alpha features are off by default. We retain the right to change or remove +alpha features without warning. An API field that is part of an alpha feature +and requires a webhook feature flag to be used also can also be removed from the +API without warning. An alpha feature might not work for all cert-manager's +supported Kubernetes versions. If you want to disable a previously enabled alpha +feature gate, you should make sure that you have updated any resources that have API +fields set that are only valid if the feature gate is enabled, else the resources +will end up in an invalid state. + +**Beta** + +All beta features are off by default. Beta features will not be removed, but may +be changed. If the feature gets changed in incompatible ways, we will provide +migration instructions. A beta feature will work with all cert-manager's +supported Kubernetes versions. If you want to disable a previously enabled beta +feature gate, you should make sure that you have updated any resources that have API +fields set that are only valid if the beta feature gate is on, else the resources +will end up in invalid state. + +**GA** + +A feature that is GA is on by default and cannot be disabled (unless it's opt in and toggled on/off by another mechanism, such as a flag). +With regards to API fields and their functionality, we keep Kubernetes API compatibility promise, see [API compatibility](./api-compatibility.md). +The feature flag for a GA feature might be left in place to avoid breaking folks, but will be non-functional. + +## Graduation + +The graduation criteria can be different for each feature. +Generally, we find user feedback most valuable when determining if a feature is sufficiently mature to graduate. If you are using an alpha or beta feature and would like to see it graduate, it would be great if you could give us some feedback about how you use it and whether you find the API useful to initiate graduation process. Feel free to [open a GitHub issue](https://github.com/cert-manager/cert-manager/issues/new/choose) or [join one of our meetings](../contributing/#meetings) to talk about this. + +## List of current feature gates + +### Alpha + +See `--feature-gates` flags on cert-manager controller and webhook to enable any of these features. + +- `AdditionalCertificateOutputFormats`. Added in cert-manager 1.7.0. Allows to specify additional formats in which cert-manager will store issued certificates and keys. See [release note](../release-notes/release-notes-1.7.md#additional-certificate-output-formats). Requires the feature to be enabled on both cert-manager controller and webhook + +- `ExperimentalCertificateSigningRequestControllers`. Added in cert-manager + 1.4.0. Allows to use Kubernetes + [CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) + resources with cert-manager. See [release notes](../release-notes/release-notes-1.4.md#experimental-support-for-kubernetes-certificatesigningrequests) + +- `ExperimentalGatewayAPISupport`. Added in cert-manager 1.5.0. Allows to use cert-manager to automatically issue certificates for `Gateway` resources as well as use `Gateway`s and `HTTPRoute`s to solve ACME HTTP-01 challenges. See [Securing Gateway resources](../usage/gateway.md) + +- `LiteralCertificateSubject`. Added in cert-manager 1.9.0. Allows to specify certificate subject in a form that can be used to define a location in LDAP directory tree. See [release notes](../release-notes/release-notes-1.9.md#literal-certificate-subjects) + +- `ServerSideApply`. Added in cert-manager 1.8.0. If this feature is enabled, cert-manager uses [Server side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) when creating or updating API resources. This will speed cert-manager operations and prevent the resource version conflict errors. See [release notes](../release-notes/release-notes-1.8.md#server-side-apply) + +- `StableCertificateRequestName`. Added in cert-manager 1.10.0. Will enable generation of `CertificateRequest` resources with a fixed name. See [`cert-manager#5487`](https://github.com/cert-manager/cert-manager/pull/5487) + +- `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.12.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) + +- `ValidateCAA`. Added in cert-manager 0.7.2. CAA checking when issuing a certificate. + + +### Beta + +There are currently no beta feature gates diff --git a/content/v1.13-docs/installation/helm.md b/content/v1.13-docs/installation/helm.md new file mode 100644 index 0000000000..055a1ce82b --- /dev/null +++ b/content/v1.13-docs/installation/helm.md @@ -0,0 +1,288 @@ +--- +title: Helm +description: 'cert-manager installation: Using Helm' +--- + +## Installing with Helm + +cert-manager provides Helm charts as a first-class method of installation on both Kubernetes and OpenShift. + +Be sure never to embed cert-manager as a sub-chart of other Helm charts; cert-manager manages +non-namespaced resources in your cluster and care must be taken to ensure that it is installed exactly once. + +### Prerequisites + +- [Install Helm version 3 or later](https://helm.sh/docs/intro/install/). +- Install a [supported version of Kubernetes or OpenShift](./supported-releases.md). +- Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. + +### Steps + +#### 1. Add the Helm repository + +This repository is the only supported source of cert-manager charts. There are some other mirrors and copies across the internet, but those are entirely unofficial and could present a security risk. + +Notably, the "Helm stable repository" version of cert-manager is deprecated and should not be used. + +```bash +helm repo add jetstack https://charts.jetstack.io +``` + +#### 2. Update your local Helm chart repository cache: + +```bash +helm repo update +``` + +#### 3. Install `CustomResourceDefinitions` + +cert-manager requires a number of CRD resources, which can be installed manually using `kubectl`, +or using the `installCRDs` option when installing the Helm chart. Both options +are described below and will achieve the same result but with varying +consequences. You should consult the [CRD Considerations](#crd-considerations) +section below for details on each method. + +##### Option 1: installing CRDs with `kubectl` + +> Recommended for production installations + +```bash +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.crds.yaml +``` + +##### Option 2: install CRDs as part of the Helm release + +> Recommended for ease of use & compatibility + +To automatically install and manage the CRDs as part of your Helm release, you +must add the `--set installCRDs=true` flag to your Helm installation command. + +Uncomment the relevant line in the next steps to enable this. + +Note that if you're using a `helm` version based on Kubernetes `v1.18` or below (Helm `v3.2`), `installCRDs` will not work with cert-manager `v0.16`. See the [v0.16 upgrade notes](./upgrading/upgrading-0.15-0.16.md#helm) for more details. + +#### 4. Install cert-manager + +To install the cert-manager Helm chart, use the [Helm install command](https://helm.sh/docs/helm/helm_install/) as described below. + +```bash +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.13.0 \ + # --set installCRDs=true +``` + +A full list of available Helm values is on [cert-manager's ArtifactHub page](https://artifacthub.io/packages/helm/cert-manager/cert-manager). + +The example below shows how to tune the cert-manager installation by overwriting the default Helm values: + +```bash +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.13.0 \ + # --set installCRDs=true + --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter + --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter +``` + +Once you have deployed cert-manager, you can [verify](./verify.md) the installation. + +### Installing cert-manager as subchart + +If you have configured cert-manager as a subchart all the components of cert-manager will be installed into the namespace of the helm release you are installing. + +There may be a situation where you want to specify the namespace to install cert-manager different to the umbrella chart's namespace. + +This is a [known issue](https://github.com/helm/helm/issues/5358) with helm and subcharts, that you can't specify the namespace for the subchart and is being solved by most public charts by allowing users to set the namespace via the values file, but needs to be a capability added to the chart by the maintainers. + +This capability is now available in the cert-manager chart and can be set either in the values file or via the `--set` switch. + +#### Example usage + +Below is an example `Chart.yaml` with cert-manager as a subchart + +```yaml +apiVersion: v2 +name: example_chart +description: A Helm chart with cert-manager as subchart +type: application +version: 0.1.0 +appVersion: "0.1.0" +dependencies: + - name: cert-manager + version: v1.13.0 + repository: https://charts.jetstack.io + alias: cert-manager + condition: cert-manager.enabled +``` + +You can then override the namespace in 2 ways: + +1. In `Values.yaml` file +```yaml +cert-manager: #defined by either the name or alias of your dependency in Chart.yaml + namespace: security +``` + +2. In the helm command using `--set` +```bash +helm install example example_chart \ + --namespace example \ + --create-namespace \ + --set cert-manager.namespace=security +``` + +The above example will install cert-manager into the security namespace. + +## Output YAML + +Instead of directly installing cert-manager using Helm, a static YAML manifest can be created using the [Helm template command](https://helm.sh/docs/helm/helm_template/). +This static manifest can be tuned by providing the flags to overwrite the default Helm values: + +```bash +helm template \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.13.0 \ + # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter + # --set installCRDs=true \ # Uncomment to also template CRDs + > cert-manager.custom.yaml +``` + +## Uninstalling + +> **Warning**: To uninstall cert-manager you should always use the same process for +> installing but in reverse. Deviating from the following process whether +> cert-manager has been installed from static manifests or Helm can cause issues +> and potentially broken states. Please ensure you follow the below steps when +> uninstalling to prevent this happening. + +Before continuing, ensure that all cert-manager resources that have been created +by users have been deleted. You can check for any existing resources with the +following command: + +```bash +kubectl get Issuers,ClusterIssuers,Certificates,CertificateRequests,Orders,Challenges --all-namespaces +``` + +Once all these resources have been deleted you are ready to uninstall +cert-manager using the procedure determined by how you installed. + +### Uninstalling with Helm + +Uninstalling cert-manager from a `helm` installation is a case of running the +installation process, *in reverse*, using the delete command on both `kubectl` +and `helm`. + +```bash +helm --namespace cert-manager delete cert-manager +``` + +Next, delete the cert-manager namespace: + +```bash +kubectl delete namespace cert-manager +``` + +Finally, delete the cert-manager +[`CustomResourceDefinitions`](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) +using the link to the version `vX.Y.Z` you installed: +> **Warning**: This command will also remove installed cert-manager CRDs. All +> cert-manager resources (e.g. `certificates.cert-manager.io` resources) will +> be removed by Kubernetes' garbage collector. + +```bash +kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/vX.Y.Z/cert-manager.crds.yaml +``` + +*Note:* If you used `helm` to install the CRDs with the `installCRDs=true` +value for the chart, then the CRDs will have been automatically removed and +you do not need to run this final `kubectl` command. + +### Namespace Stuck in Terminating State + +If the namespace has been marked for deletion without deleting the cert-manager +installation first, the namespace may become stuck in a terminating state. This +is typically due to the fact that the [`APIService`](https://kubernetes.io/docs/tasks/access-kubernetes-api/setup-extension-api-server) resource still exists +however the webhook is no longer running so is no longer reachable. To resolve +this, ensure you have run the above commands correctly, and if you're still +experiencing issues then run: + +```bash +kubectl delete apiservice v1beta1.webhook.cert-manager.io +``` + +## CRD considerations + +### kubectl installation + +When installing CRDs with `kubectl`, you will need to upgrade these in tandem +with your cert-manager installation upgrades. This approach may be useful when +you do not have the ability to install CRDs all the time in your environment. +If you do not upgrade these as you upgrade cert-manager itself, you may miss +out on new features for cert-manager. + +Benefits: + +- CRDs will not change once applied + +Drawbacks: + +- CRDs are not automatically updated and need to be reapplied before + upgrading cert-manager +- You may have different installation processes for CRDs compared to + the other resources. + +### helm installation + +cert-manager **does not use** the [official helm method](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/ ) +of installing CRD resources. This is because it makes upgrading CRDs +impossible with `helm` CLI alone. The helm team explain the limitations +of their approach [here](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations). + +cert-manager actually bundles the CRDs along with the other templates +in the Helm chart. This means that Helm manages these resources so they are +upgraded with your cert-manager release when you use +`installCRDs: true` in your values file or CLI command. This does also mean +that if you uninstall the release, the CRDs will also be uninstalled. If that +happens then you will loose all instances of those CRDs, e.g. all `Certificate` +resources in the cluster. You should consider if this is likely to happen to +you and have a mitigation, such as +[backups](https://cert-manager.io/docs/tutorials/backup/#backing-up-cert-manager-resource-configuration) +or a means to reapply resources from an Infrastructure as Code (IaC) pattern. + +**Note** this also means a typo like `installCRD: true` would be an invalid +value and helm would silently ignore this and remove the CRDs when you next +run your `helm upgrade`. + +Benefits: + +- CRDS are automatically updated when you upgrade cert-manager via `helm` +- Same action manages both CRDs and other installation resources + +Drawbacks: + +- If you uninstall cert-manager, the CRDs are also uninstalled. +- Helm values need to be correct to avoid accidental removal of CRDs. + +### CRD Installation Advice + +> You should follow the path that makes sense for your environment. + +Generally we recommend: + +- For **Safety**, install CRDs outside of Helm, e.g. `kubectl` +- For **Ease of use**, install CRDS with `helm` + +You may want to consider your approach along with other tools that may offer +helm compatible installs, for a standardized approach to managing CRD +resources. If you have an approach that cert-manager does not currently +support, then please +[raise an issue](https://github.com/cert-manager/cert-manager/issues) to +discuss. + diff --git a/content/v1.13-docs/installation/kubectl.md b/content/v1.13-docs/installation/kubectl.md new file mode 100644 index 0000000000..225b229662 --- /dev/null +++ b/content/v1.13-docs/installation/kubectl.md @@ -0,0 +1,125 @@ +--- +title: kubectl apply +description: Learn how to install cert-manager using kubectl and static manifests +--- + +Learn how to install cert-manager using kubectl and static manifests. + +## Prerequisites + +- [Install `kubectl` version `>= v1.19.0`](https://kubernetes.io/docs/tasks/tools/). (otherwise, you'll have issues updating the CRDs - see [v0.16 upgrade notes](./upgrading/upgrading-0.15-0.16.md#issue-with-older-versions-of-kubectl)) +- Install a [supported version of Kubernetes or OpenShift](./supported-releases.md). +- Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. + +## Steps + +All resources (the [`CustomResourceDefinitions`](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions) and the cert-manager, cainjector and webhook components) +are included in a single YAML manifest file: + +Install all cert-manager components: + +```bash +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml +``` + +By default, cert-manager will be installed into the `cert-manager` +namespace. It is possible to run cert-manager in a different namespace, although +you'll need to make modifications to the deployment manifests. + +Once you have deployed cert-manager, you can [verify the installation](./verify.md). + +## Permissions Errors on Google Kubernetes Engine + +When running on GKE (Google Kubernetes Engine), you might encounter a 'permission denied' error when creating some +of the required resources. This is a nuance of the way GKE handles RBAC and IAM permissions, +and as such you might need to elevate your own privileges to that of a "cluster-admin" **before** +running `kubectl apply`. + +If you have already run `kubectl apply`, you should run it again after elevating your permissions: + +```bash +kubectl create clusterrolebinding cluster-admin-binding \ + --clusterrole=cluster-admin \ + --user=$(gcloud config get-value core/account) +``` + +## Uninstalling +> **Warning**: To uninstall cert-manager you should always use the same process for +> installing but in reverse. Deviating from the following process whether +> cert-manager has been installed from static manifests or Helm can cause issues +> and potentially broken states. Please ensure you follow the below steps when +> uninstalling to prevent this happening. + +Before continuing, ensure that unwanted cert-manager resources that have been created +by users have been deleted. You can check for any existing resources with the +following command: + +```bash +kubectl get Issuers,ClusterIssuers,Certificates,CertificateRequests,Orders,Challenges --all-namespaces +``` +It is recommended that you delete all these resources before uninstalling cert-manager. +If you plan on reinstalling later and don't want to lose some custom resources, you can keep them. +However, this can potentially lead to problems with finalizers. Some resources, like +`Challenges`, should be deleted to avoid [getting stuck in a pending state](#namespace-stuck-in-terminating-state). + +Once the unneeded resources have been deleted, you are ready to uninstall +cert-manager using the procedure determined by how you installed. + +> **Warning**: Uninstalling cert-manager or simply deleting a `Certificate` resource can result in +> TLS `Secret`s being deleted if they have `metadata.ownerReferences` set by cert-manager. +> You can control whether owner references are added to `Secret`s using the `--enable-certificate-owner-ref` controller flag. +> By default, this flag is set to false, which means that no owner references are added. +> However, in cert-manager v1.8 and older, changing the flag's value from true to false _did not_ +> result in existing owner references being removed. This behavior was fixed in cert-manager v1.8. +> Do check the owner references to confirm that they actually are removed. + +### Uninstalling with regular manifests + +Uninstalling from an installation with regular manifests is a case of running +the installation process, *in reverse*, using the delete command of `kubectl`. + +Delete the installation manifests using a link to your currently running version +`vX.Y.Z` like so: +> **Warning**: This command will also remove installed cert-manager CRDs. All +> cert-manager resources (e.g. `certificates.cert-manager.io` resources) will +> be removed by Kubernetes' garbage collector. +> You cannot keep any custom resources if you delete the `CustomResourceDefinition`s. +> If you want to keep resources, you should manage `CustomResourceDefinition`s separately. + +```bash +kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/vX.Y.Z/cert-manager.yaml +``` + +### Namespace Stuck in Terminating State + +If the namespace has been marked for deletion without deleting the cert-manager +installation first, the namespace may become stuck in a terminating state. This +is typically due to the fact that the [`APIService`](https://kubernetes.io/docs/tasks/access-kubernetes-api/setup-extension-api-server) resource still exists +however the webhook is no longer running so is no longer reachable. To resolve +this, ensure you have run the above commands correctly, and if you're still +experiencing issues then run: + +```bash +kubectl delete apiservice v1beta1.webhook.cert-manager.io +``` + +#### Deleting pending challenges + +`Challenge`s can get stuck in a pending state when the finalizer is unable to complete +and Kubernetes is waiting for the cert-manager controller to finish. +This happens when the controller is no longer running to remove the flag, +and the resources are defined as needing to wait. +You can fix this problem by doing what the controller does manually. + +First, delete existing cert-manager webhook configurations, if any: + +```bash +kubectl delete mutatingwebhookconfigurations cert-manager-webhook +kubectl delete validatingwebhookconfigurations cert-manager-webhook +``` + +Then change the `.metadata.finalizers` field to an empty list by editing the challenge resource: + +```bash +kubectl edit challenge +``` diff --git a/content/v1.13-docs/installation/operator-lifecycle-manager.md b/content/v1.13-docs/installation/operator-lifecycle-manager.md new file mode 100644 index 0000000000..eb431286ff --- /dev/null +++ b/content/v1.13-docs/installation/operator-lifecycle-manager.md @@ -0,0 +1,243 @@ +--- +title: Operator Lifecycle Manager +description: 'cert-manager installation: Using OLM' +--- + +## Installation managed by OLM + +### Prerequisites + +- Install a [supported version of Kubernetes or OpenShift](./supported-releases.md). +- Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. + +### Option 1: Installing from OperatorHub Web Console on OpenShift + +cert-manager is in the [Red Hat-provided Operator catalog][] called "community-operators". +On OpenShift 4 you can install cert-manager from the [OperatorHub web console][] or from the command line. +These installation methods are described in Red Hat's [Adding Operators to a cluster][] documentation. + +> ⚠️ In cert-manager 1.10 the [secure computing (seccomp) profile](https://kubernetes.io/docs/tutorials/security/seccomp/) for all the Pods +> is set to `RuntimeDefault`. +> On some versions and configurations of OpenShift this can cause the Pod to be rejected by the +> [Security Context Constraints admission webhook](https://docs.openshift.com/container-platform/4.10/authentication/managing-security-context-constraints.html#admission_configuring-internal-oauth). +> +> 📖 Read the [Breaking Changes section in the 1.10 release notes](https://cert-manager.io/docs/release-notes/release-notes-1.10/#on-openshift-the-cert-manager-pods-may-fail-until-you-modify-security-context-constraints) before installing or upgrading from an older version to 1.10 or newer. + +[Red Hat-provided Operator catalog]: https://docs.openshift.com/container-platform/4.7/operators/understanding/olm-rh-catalogs.html#olm-rh-catalogs_olm-rh-catalogs +[OperatorHub web console]: https://docs.openshift.com/container-platform/4.7/operators/understanding/olm-understanding-operatorhub.html +[Adding Operators to a cluster]: https://docs.openshift.com/container-platform/4.7/operators/admin/olm-adding-operators-to-cluster.html + + +### Option 2: Installing from OperatorHub.io + +Browse to the [cert-manager page on OperatorHub.io](https://operatorhub.io/operator/cert-manager), +click the "Install" button and follow the installation instructions. + +### Option 3: Manual install via `kubectl operator` plugin + +[Install OLM][] and [install the `kubectl operator` plugin][] +from the [Krew Kubectl plugins index][] and then use that to install the cert-manager as follows: + +```sh +operator-sdk olm install +kubectl krew install operator +kubectl operator install cert-manager -n operators --channel stable --approval Automatic +``` + +You can monitor the progress of the installation as follows: + +```sh +kubectl get events -w -n operators +``` + +And you can see the status of the installation with: + +```sh +kubectl operator list +``` + +[install OLM]: https://sdk.operatorframework.io/docs/installation/ +[install the `kubectl operator` plugin]: https://github.com/operator-framework/kubectl-operator#install +[Krew Kubectl plugins index]: https://krew.sigs.k8s.io/plugins/#:~:text=cert-manager + +## Release Channels + +Whichever installation method you chose, there will now be an [OLM Subscription resource][] for cert-manager, +tracking the "stable" release channel. E.g. + +```console +$ kubectl get subscription cert-manager -n operators -o yaml +... +spec: + channel: stable + installPlanApproval: Automatic + name: cert-manager +... +status: + currentCSV: cert-manager.v1.7.1 + state: AtLatestKnown +... +``` + +This means that OLM will discover new cert-manager releases in the stable channel, +and, depending on the Subscription settings it will upgrade cert-manager automatically, +when new releases become available. +Read [Manually Approving Upgrades via Subscriptions][] for information about automatic and manual upgrades. + +[OLM Subscription resource]: https://olm.operatorframework.io/docs/concepts/crds/subscription/ +[Manually Approving Upgrades via Subscriptions]: https://olm.operatorframework.io/docs/concepts/crds/subscription/#manually-approving-upgrades-via-subscriptions + +**NOTE:** There is a single release channel called "stable" which will contain all cert-manager releases, shortly after they are released. +In future we may introduce other release channels with alternative release schedules, +in accordance with [OLM's Recommended Channel Naming][]. + +[OLM's Recommended Channel Naming]: https://olm.operatorframework.io/docs/best-practices/channel-naming/#recommended-channel-naming + +## Debugging installation issues + +If you have any issues with your installation, please refer to the +[FAQ](../faq/README.md). + +## Configuration + +The configuration options are quite limited when you install cert-manager using OLM. +There are a few Deployment settings which can be overridden permanently in the Subscription +and most other elements of the cert-manager manifests can be changed by editing the ClusterServiceVersion, +but changes to the ClusterServiceVersion are temporary and will be lost if OLM upgrades cert-manager, +because an upgrade results in a new ClusterServiceVersion resource. + +### Configuration Via Subscription + +When you create an OLM Subscription you can override **some** of the cert-manager Deployment settings, +but the options are quite limited. +The configuration which you add to the Subscription will be applied immediately to the current cert-manager Deployments. +It will also be re-applied if OLM upgrades cert-manager. + +> 🔰 Read the [Configuring Operators deployed by OLM](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/doc/design/subscription-config.md#configuring-operators-deployed-by-olm) design doc in the OLM repository. +> +> 🔰 Refer to the [Subscription API documentation](https://pkg.go.dev/github.com/operator-framework/api@v0.14.0/pkg/operators/v1alpha1#Subscription). + +Here are some examples of configuration that can be achieved by modifying the Subscription resource. +In each case we assume that you are starting with the following [default Subscription from OperatorHub.io](https://operatorhub.io/install/cert-manager.yaml): + +```yaml +# cert-manager.yaml +apiVersion: operators.coreos.com/v1alpha1 +kind: Subscription +metadata: + name: my-cert-manager + namespace: operators +spec: + channel: stable + name: cert-manager + source: operatorhubio-catalog + sourceNamespace: olm +``` + +```bash +kubectl create -f https://operatorhub.io/install/cert-manager.yaml +``` + +#### Change the Resource Requests and Limits + +It is possible to change the resource requests and limits by adding a `config` stanza to the Subscription: + +```yaml +# resources-patch.yaml +spec: + config: + resources: + requests: + memory: "64Mi" + cpu: "250m" + limits: + memory: "128Mi" + cpu: "500m" +``` + + +```bash +kubectl -n operators patch subscription my-cert-manager --type merge --patch-file resources-patch.yaml +``` + +You will see **all** the cert-manager Pods are restarted with the new resources: + +```console +$ kubectl -n operators get pods -o "custom-columns=name:.metadata.name,mem:.spec.containers[*].resources" +name mem +cert-manager-669867589c-n8dcn map[limits:map[cpu:500m memory:128Mi] requests:map[cpu:250m memory:100Mi]] +cert-manager-cainjector-7b7fff8b9c-dxw6b map[limits:map[cpu:500m memory:128Mi] requests:map[cpu:250m memory:100Mi]] +cert-manager-webhook-975bc87b5-tqdj4 map[limits:map[cpu:500m memory:128Mi] requests:map[cpu:250m memory:100Mi]] +``` + +> ⚠️ This configuration will apply to **all** the cert-manager Deployments. +> This is a known limitation of OLM which [does not support configuration of individual Deployments](https://github.com/operator-framework/operator-lifecycle-manager/issues/1794). + +#### Change the NodeSelector + +It is possible to change the `nodeSelector` for cert-manager Pods by adding the following stanza to the Subscription: + +```yaml +# nodeselector-patch.yaml +spec: + config: + nodeSelector: + kubernetes.io/arch: amd64 +``` + +```bash +kubectl -n operators patch subscription my-cert-manager --type merge --patch-file nodeselector-patch.yaml +``` + +You will see **all** the cert-manager Pods are restarted with the new `nodeSelector`: + +```console +$ kubectl -n operators get pods -o "custom-columns=name:.metadata.name,nodeselector:.spec.nodeSelector" +name nodeselector +cert-manager-5b6b8f7d74-k7l94 map[kubernetes.io/arch:amd64 kubernetes.io/os:linux] +cert-manager-cainjector-b89cd6f46-kdkk2 map[kubernetes.io/arch:amd64 kubernetes.io/os:linux] +cert-manager-webhook-8464bc7cc8-64b4w map[kubernetes.io/arch:amd64 kubernetes.io/os:linux] +``` + +> ⚠️ This configuration will apply to **all** the cert-manager Deployments. +> This is a known limitation of OLM which [does not support configuration of individual Deployments](https://github.com/operator-framework/operator-lifecycle-manager/issues/1794). + +### Configuration Via ClusterServiceVersion (CSV) + +The ClusterServiceVersion (CSV) resource contains the templates for all the cert-manager Deployments. +If you patch these templates, OLM will immediately roll out the changes to the Deployments. + +> ⚠️ If OLM upgrades cert-manager your changes will be lost because it will create a new CSV with default Deployment templates. + +Nevertheless, editing (patching) the CSV can be a useful way to override certain cert-manager settings. An example: + +#### Change the log level of cert-manager components + +The following JSON patch will append `-v=6` to command line arguments of the cert-manager controller-manager +(the first container of the first Deployment). + +```bash +kubectl patch csv cert-manager.v1.13.0 \ + --type json \ + -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' +``` + +You will see the controller-manager Pod is restarted with the new arguments. + +```console +$ kubectl -n operators get pods -o "custom-columns=name:.metadata.name,args:.spec.containers[0].args" +name args +cert-manager-797979cbdb-g444r [-v=2 --cluster-resource-namespace=$(POD_NAMESPACE) --leader-election-namespace=kube-system -v=6] +... +``` + +> 🔰 Refer to the [ClusterServiceVersion API documentation](https://pkg.go.dev/github.com/operator-framework/api@v0.14.0/pkg/operators/v1alpha1#ClusterServiceVersion). + +## Uninstall + +Below is the processes for uninstalling cert-manager on OpenShift. + +> ⚠️ To uninstall cert-manager you should always use the same process for +> installing but in reverse. Deviating from the following process can cause +> issues and potentially broken states. Please ensure you follow the below steps +> when uninstalling to prevent this happening. diff --git a/content/v1.13-docs/installation/other-tools.md b/content/v1.13-docs/installation/other-tools.md new file mode 100644 index 0000000000..6d7b0b7044 --- /dev/null +++ b/content/v1.13-docs/installation/other-tools.md @@ -0,0 +1,23 @@ +--- +title: Alternative installation methods +description: 'cert-manager installation: Other tools' +--- + +### kubeprod + +[Bitnami Kubernetes Production +Runtime](https://github.com/bitnami/kube-prod-runtime) (`BKPR`, `kubeprod`) is a +curated collection of the services you would need to deploy on top of your +Kubernetes cluster to enable logging, monitoring, certificate management, +automatic discovery of Kubernetes resources via public DNS servers and other +common infrastructure needs. + +It depends on `cert-manager` for certificate management, and it is [regularly +tested](https://github.com/bitnami/kube-prod-runtime/blob/master/Jenkinsfile) so +the components are known to work together for GKE, AKS, and EKS clusters. For +its ingress stack it creates a DNS entry in the configured DNS zone and requests +a TLS certificate from the Let's Encrypt staging server. + +BKPR can be deployed using the `kubeprod install` command, which will deploy +`cert-manager` as part of it. Details available in the [BKPR installation +guide](https://github.com/bitnami/kube-prod-runtime/blob/master/docs/install.md). \ No newline at end of file diff --git a/content/v1.13-docs/installation/supported-releases.md b/content/v1.13-docs/installation/supported-releases.md new file mode 100644 index 0000000000..29e5ba4d9a --- /dev/null +++ b/content/v1.13-docs/installation/supported-releases.md @@ -0,0 +1,296 @@ +--- +title: Supported Releases +description: Supported releases, Kubernetes versions, OpenShift versions and upcoming release timeline +--- + +{/* +Inspired by https://istio.io/latest/about/supported-releases/ +*/} + +This page lists the status, timeline and policy for currently supported releases. + +Each release is supported for a period of four months, and we aim to create a new +release roughly every two months, accounting for holiday periods, major conferences +and other world events. + + +## Currently supported releases + +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| +| [1.13][] | Sep 12, 2023 | Release of 1.15 | 1.23 → 1.28 | 4.10 → 4.15 | +| [1.13.0] | May 19, 2023 | Release of 1.14 | 1.22 → 1.27 | 4.9 → 4.14 | + +## Upcoming releases + +| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | +|----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| +| [1.14][] | Jan 15, 2024 | ~4 months post release | TBD | TBD | + +Dates in the future are uncertain and might change. + +## Old releases + +| Release | Release Date | EOL | Compatible Kubernetes versions | Compatible OpenShift versions | +|----------|:------------:|:------------:|:------------------------------:|:-----------------------------:| +| [1.11][] | Jan 11, 2023 | Sep 12, 2023 | 1.21 → 1.27 | 4.8 → 4.14 | +| [1.10][] | Oct 17, 2022 | May 19, 2023 | 1.20 → 1.26 | 4.7 → 4.13 | +| [1.9][] | Jul 22, 2022 | Jan 11, 2023 | 1.20 → 1.24 | 4.7 → 4.11 | +| [1.8][] | Apr 05, 2022 | Oct 17, 2022 | 1.19 → 1.24 | 4.6 → 4.11 | +| [1.7][] | Jan 26, 2021 | Jul 22, 2022 | 1.18 → 1.23 | 4.5 → 4.9 | +| [1.6][] | Oct 26, 2021 | Apr 05, 2022 | 1.17 → 1.22 | 4.4 → 4.9 | +| [1.5][] | Aug 11, 2021 | Jan 26, 2022 | 1.16 → 1.22 | 4.3 → 4.8 | +| [1.4][] | Jun 15, 2021 | Oct 26, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | +| [1.3][] | Apr 08, 2021 | Aug 11, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | +| [1.2][] | Feb 10, 2021 | Jun 15, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | +| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | +| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | +| [0.11][] | Oct 10, 2019 | Jan 21, 2020 | 1.9 → 1.21 | 3.09 → 4.7 | + +[s]: #kubernetes-supported-versions +[1.13]: https://github.com/cert-manager/cert-manager/milestone/34 +[1.13.0 https://cert-manager.io/docs/release-notes/release-notes-1.12 +[1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 +[1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 +[1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 +[1.8]: https://cert-manager.io/docs/release-notes/release-notes-1.8 +[1.7]: https://cert-manager.io/docs/release-notes/release-notes-1.7 +[1.6]: https://cert-manager.io/docs/release-notes/release-notes-1.6 +[1.5]: https://cert-manager.io/docs/release-notes/release-notes-1.5 +[1.4]: https://cert-manager.io/docs/release-notes/release-notes-1.4 +[1.3]: https://cert-manager.io/docs/release-notes/release-notes-1.3 +[1.2]: https://cert-manager.io/docs/release-notes/release-notes-1.2 +[1.1]: https://cert-manager.io/docs/release-notes/release-notes-1.1 +[1.0]: https://cert-manager.io/docs/release-notes/release-notes-1.0 +[0.16]: https://cert-manager.io/docs/release-notes/release-notes-0.16 +[0.15]: https://cert-manager.io/docs/release-notes/release-notes-0.15 +[0.14]: https://cert-manager.io/docs/release-notes/release-notes-0.14 +[0.13]: https://cert-manager.io/docs/release-notes/release-notes-0.13 +[0.12]: https://cert-manager.io/docs/release-notes/release-notes-0.12 +[0.11]: https://cert-manager.io/docs/release-notes/release-notes-0.11 + +We list cert-manager releases on [GitHub](https://github.com/cert-manager/cert-manager/releases), +and release notes on [cert-manager.io](https://cert-manager.io/docs/release-notes/). + +We also maintain detailed [upgrade instructions](https://cert-manager.io/docs/installation/upgrading/). + +## Support policy + +### What we mean by support + +Our support window is four months for each release branch. In the below +diagram, `release-1.2` is an example of a release branch. The support +window corresponds to the two latest releases, given that we produce a new +final release every two months. We offer two types of support: + +- [Technical support](#technical-support), +- [Security and bug fixes](#bug-fixes-support). + +For example, imagining that the latest release is `v1.2.0`, you can expect +support for both `v1.2.0` and `v1.1.0`. Only the last patch release of each +branch is actually supported. + +```diagram + v1.0.0 ^ + Sep 2, 2020 | UNSUPPORTED +------+---------------------------------------------> release-1.0 | RELEASES + \ v + \ + \ v1.1.0 + \ Nov 24, 2020 ^ + ---------+-------------------------------> release-1.1 | + \ | SUPPORTED + \ | RELEASES + \ v1.2.0 | = the two + \ Feb 10, 2021 | last + ------------+--------------> release-1.2 | releases + \ v + \ + \ + \ + -----------> master branch + April 1, 2021 +``` + + +### Technical support + +Technical assistance is offered on a best-effort basis for supported +releases only. You can request support from the community on [Kubernetes +Slack](https://slack.k8s.io/) (in the `#cert-manager` channel), using +[GitHub Discussions][discussions] or using the [cert-manager-dev][group] +Google group. + +[discussions]: https://github.com/cert-manager/cert-manager/discussions +[group]: https://groups.google.com/g/cert-manager-dev + + +### Security and bug fixes + +We back-port important bug fixes — including security fixes — to all +currently supported releases. + +- [Security issues](#security-issues), +- [Critical bugs](#critical-bugs), +- [Long-standing bugs](#long-standing-bugs). + + +#### Security issues + +**Security issues** are fixed as soon as possible. They get back-ported to +the last two releases, and a new patch release is immediately created for them. + + +#### Critical bugs + +**Critical bugs** include both regression bugs as well as upgrade bugs. + +Regressions are functionalities that worked in a previous release but no longer +work. [#4142][], [#3393][] and [#2857][] are three examples of regressions. + +Upgrade bugs are issues (often Helm-related) preventing users from +upgrading to currently supported releases from earlier releases of +cert-manager. [#3882][] and [#3644][] are examples of upgrade bugs. + +Note that [intentional breaking changes](#breaking-changes) do not belong to +this category. + +Fixes for critical bugs are (usually) immediately back-ported by creating a new +patch release for the currently supported releases. + + +#### Long-standing bugs + +**Long-standing bug**: sometimes a bug exists for a long time, and may have +known workarounds. [#3444][] is an example of a long-standing bug. + +Where we feel that back-porting would be difficult or might be a stability +risk to clusters running cert-manager, we'll make the fix in a major +release but avoid back-porting the fix. + + +#### Breaking changes + +Breaking changes are changes that intentionally break the cert-manager +Kubernetes API or the command line flags. We avoid making breaking changes +where possible, and where they're required we'll give as much notice as +possible. + + +#### Other back-ports + +We aim to be conservative in what we back-port. That applies especially for anything which +could be a _runtime_ change - that is, a change which might alter behavior for someone +upgrading between patch releases. + +That means that if a candidate for back-porting has a chance of having a runtime impact we're +unlikely to accept the change unless it addresses a security issue or a critical bug. + +We reserve the right to back-port other changes which are unlikely to have a runtime impact, such as +documentation or tooling changes. An example would be [#5209][] which updated how we perform a release of +cert-manager but didn't have any realistic chance of having a runtime impact. + +Generally we'll seek to be pragmatic. A rule of thumb might be to ask: + +"Does this back-port improve cert-manager, bearing in mind that we really value stability for already-released versions?" + +[#3393]: https://github.com/cert-manager/cert-manager/issues/3393 "Broken CloudFlare DNS01 challenge" +[#2857]: https://github.com/cert-manager/cert-manager/issues/2857 "CloudDNS DNS01 challenge crashes cert-manager" +[#4142]: https://github.com/cert-manager/cert-manager/issues/4142 "Cannot issue a certificate that has the same subject and issuer" +[#3444]: https://github.com/cert-manager/cert-manager/issues/3444 "Certificates do not get immediately updated after updating them" +[#3882]: https://github.com/cert-manager/cert-manager/pull/3882 "Certificate's revision history limit validated by webhook" +[#3644]: https://github.com/cert-manager/cert-manager/issues/3644 "Helm upgrade from v1.2 to v1.2 impossible due to a Helm bug" +[#5209]: https://github.com/cert-manager/cert-manager/pull/5209 "release-1.8: rclone" + + + +## How we determine supported Kubernetes versions + +The list of supported Kubernetes versions displayed in the [Supported Releases](#supported-releases) section +depends on what the cert-manager maintainers think is reasonable to support and to test. + +In practice, this is largely determined based on what versions of [kind](https://github.com/kubernetes-sigs/kind) +are available for testing, and which versions of Kubernetes are provided by major upstream cloud Kubernetes vendors +including EKS, GKE, AKS and OpenShift. + +| Vendor | Oldest Kubernetes Release\* | Other Older Kubernetes Releases | +|:-----------------:|-----------------------------|------------------------------------------------------------------------------------| +| [EKS][eks] | 1.23 (EOL Oct 2023) | 1.24 (EOL Jan 2024), 1.25 (EOL May 2024), 1.26 (EOL Jun 2024), 1.28 (EOL Nov 2024) | +| [GKE][gke] | 1.24 (EOL Oct 2023) | 1.25 (EOL Feb 2024), 1.26 (EOL May 2024), 1.27 (EOL Jan 2025), 1.28 (EOL -) | +| [AKS][aks] | 1.25 (EOL Dec 2023) | 1.26 (EOL Mar 2024), 1.27 (EOL Jun 2024), 1.28 (EOL -) | +| [OpenShift 4][os] | 1.23 (4.10, EOL Sep 2023) | 1.24 (4.11, EOL Feb 2024), 1.25 (4.12, EOL Jan 2025), 1.25 (4.13, EOL Nov 2024) | + +\*Oldest release relevant to the next cert-manager release, as of 2023-09-13 + +[eks]: https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html#kubernetes-release-calendar +[gke]: https://cloud.google.com/kubernetes-engine/docs/release-schedule +[aks]: https://docs.microsoft.com/en-us/azure/aks/supported-kubernetes-versions#aks-kubernetes-release-calendar +[os]: https://access.redhat.com/support/policy/updates/openshift#dates + +### OpenShift + +cert-manager supports versions of OpenShift 4 based on the version of Kubernetes +that each version maps to. + +For convenience, the following table shows these version mappings: + +| OpenShift versions | Kubernetes version | +|--------------------|--------------------| +| 4.15 | 1.28 | +| 4.14 | 1.27 | +| 4.13 | 1.26 | +| 4.12 | 1.25 | +| 4.11 | 1.24 | +| 4.10, 4.10 EUS | 1.23 | +| 4.9 | 1.22 | +| 4.8, 4.8 EUS | 1.21 | +| 4.7 | 1.20 | +| 4.6, 4.6 EUS | 1.19 | + +Note that some OpenShift versions listed above may be predicted, since an updated version of OpenShift may +not yet be available for the latest Kubernetes releases. + +The last version of cert-manager to support OpenShift 3 was cert-manager 1.2, which is +no longer maintained. + +## Terminology + +The term "release" (or "minor release") refers to one minor version of +cert-manager. For example, 1.2 and 1.3 are two releases. Note that we do +not use the prefix `v` for releases (just "1.2"). This is because releases +are not used as git tags. + +Patch releases use the `v` prefix (e.g., `v1.2.0`, `v1.3.1`...) since one +patch release = one git tag. The initial patch release is called "final +release": + +| Type of release | Example of git tag | Corresponding release | Corresponding release branch\* | +| --------------- | ------------------ | --------------------- | ------------------------------ | +| Final release | `v1.3.0` | 1.3 | `release-1.3` | +| Patch release | `v1.3.1` | 1.3 | `release-1.3` | +| Pre-release | `v1.4.0-alpha.0` | N/A\*\* | `release-1.4` | + +\*For maintainers: each release has an associated long-lived branch that we +call the “release branch”. For example, `release-1.2` is the release branch +for release 1.2. + +\*\*Pre-releases (e.g., `v1.3.0-alpha.0`) don't have a corresponding +release (e.g., 1.3) since a release only exists after a final release +(e.g., `v1.3.0`) has been created. + +Our naming scheme mostly follows [Semantic Versioning +2.0.0](https://semver.org/) with `v` prepended to git tags and docker +images: + +```plain +v.. +``` + +where `` is increased for each release, and `` counts the +number of patches for the current `` release. A patch is usually a +small change relative to the `` release. diff --git a/content/v1.13-docs/installation/uninstall.md b/content/v1.13-docs/installation/uninstall.md new file mode 100644 index 0000000000..de06fbc9e7 --- /dev/null +++ b/content/v1.13-docs/installation/uninstall.md @@ -0,0 +1,14 @@ +--- +title: Uninstall +description: 'cert-manager installation: Uninstalling cert-manager' +--- + +cert-manager supports running on [Kubernetes](https://kubernetes.io) and +[OpenShift](https://www.openshift.com). The uninstallation process between the +two platforms is similar. Select the method that was used for installing +cert-manager to go to the relevant uninstall documentation. + +- [kubectl](./kubectl.md#uninstalling) +- [helm](./helm.md#uninstalling) + +If you need to preserve cert-manager custom resources (`Certificate`s, `Issuer`s etc), that are not version controlled or backed up by other means, take a look at our [backup and restore guide](../tutorials/backup.md). diff --git a/content/v1.13-docs/installation/verify.md b/content/v1.13-docs/installation/verify.md new file mode 100644 index 0000000000..4a169f1c95 --- /dev/null +++ b/content/v1.13-docs/installation/verify.md @@ -0,0 +1,122 @@ +--- +title: Verifying the Installation +description: 'cert-manager installation: Verifying an upgrade was successful' +--- + +## Check cert-manager API + +First, make sure that [cmctl is installed](../reference/cmctl.md#installation). + +cmctl performs a dry-run certificate creation check against the Kubernetes cluster. +If successful, the message `The cert-manager API is ready` is displayed. + +```bash +$ cmctl check api +The cert-manager API is ready +``` + +The command can also be used to wait for the check to be successful. +Here is an output example of running the command at the same time that cert-manager is being installed: + +```bash +$ cmctl check api --wait=2m +Not ready: the cert-manager CRDs are not yet installed on the Kubernetes API server +Not ready: the cert-manager CRDs are not yet installed on the Kubernetes API server +Not ready: the cert-manager webhook deployment is not ready yet +Not ready: the cert-manager webhook deployment is not ready yet +Not ready: the cert-manager webhook deployment is not ready yet +Not ready: the cert-manager webhook deployment is not ready yet +The cert-manager API is ready +``` + +## Manual verification + +Once you've installed cert-manager, you can verify it is deployed correctly by +checking the `cert-manager` namespace for running pods: + +```bash +$ kubectl get pods --namespace cert-manager + +NAME READY STATUS RESTARTS AGE +cert-manager-5c6866597-zw7kh 1/1 Running 0 2m +cert-manager-cainjector-577f6d9fd7-tr77l 1/1 Running 0 2m +cert-manager-webhook-787858fcdb-nlzsq 1/1 Running 0 2m +``` + +You should see the `cert-manager`, `cert-manager-cainjector`, and +`cert-manager-webhook` pods in a `Running` state. The webhook might take a +little longer to successfully provision than the others. + +If you experience problems, first check the [FAQ](../faq/README.md). + +Create an `Issuer` to test the webhook works okay. +```bash +$ cat < test-resources.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: cert-manager-test +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: test-selfsigned + namespace: cert-manager-test +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: selfsigned-cert + namespace: cert-manager-test +spec: + dnsNames: + - example.com + secretName: selfsigned-cert-tls + issuerRef: + name: test-selfsigned +EOF +``` + +Create the test resources. +```bash +$ kubectl apply -f test-resources.yaml +``` + +Check the status of the newly created certificate. You may need to wait a few +seconds before cert-manager processes the certificate request. +```bash +$ kubectl describe certificate -n cert-manager-test + +... +Spec: + Common Name: example.com + Issuer Ref: + Name: test-selfsigned + Secret Name: selfsigned-cert-tls +Status: + Conditions: + Last Transition Time: 2019-01-29T17:34:30Z + Message: Certificate is up to date and has not expired + Reason: Ready + Status: True + Type: Ready + Not After: 2019-04-29T17:34:29Z +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CertIssued 4s cert-manager Certificate issued successfully +``` + +Clean up the test resources. +```bash +$ kubectl delete -f test-resources.yaml +``` + +If all the above steps have completed without error, you're good to go! + +## Community-maintained tool + +Alternatively, to automatically check if cert-manager is correctly configured, +you can run the community-maintained [cert-manager-verifier](https://github.com/alenkacz/cert-manager-verifier) tool. diff --git a/content/v1.13-docs/manifest.json b/content/v1.13-docs/manifest.json new file mode 100644 index 0000000000..f0efd6c6ca --- /dev/null +++ b/content/v1.13-docs/manifest.json @@ -0,0 +1,514 @@ +{ + "routes": [ + { + "title": "cert-manager", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/README.md" + }, + { + "title": "Getting Started", + "path": "/v1.13-docs/getting-started/README.md" + }, + { + "title": "Installation", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/installation/README.md" + }, + { + "title": "Cloud Compatibility", + "path": "/v1.13-docs/installation/compatibility.md" + }, + { + "title": "kubectl apply", + "path": "/v1.13-docs/installation/kubectl.md" + }, + { + "title": "Helm", + "path": "/v1.13-docs/installation/helm.md" + }, + { + "title": "OperatorHub (OLM)", + "path": "/v1.13-docs/installation/operator-lifecycle-manager.md" + }, + { + "title": "Other tools", + "path": "/v1.13-docs/installation/other-tools.md" + }, + { + "title": "Verifying", + "path": "/v1.13-docs/installation/verify.md" + }, + { + "title": "Feature flags", + "path": "/v1.13-docs/installation/featureflags.md" + }, + { + "title": "Uninstall", + "path": "/v1.13-docs/installation/uninstall.md" + }, + { + "title": "API compatibility", + "path": "/v1.13-docs/installation/api-compatibility.md" + }, + { + "title": "Signature Verification", + "path": "/v1.13-docs/installation/code-signing.md" + }, + { + "title": "Best Practice", + "path": "/v1.13-docs/installation/best-practice.md" + } + ] + }, + { + "title": "Configuring Issuers", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/configuration/README.md" + }, + { + "title": "SelfSigned", + "path": "/v1.13-docs/configuration/selfsigned.md" + }, + { + "title": "CA", + "path": "/v1.13-docs/configuration/ca.md" + }, + { + "title": "Vault", + "path": "/v1.13-docs/configuration/vault.md" + }, + { + "title": "Venafi", + "path": "/v1.13-docs/configuration/venafi.md" + }, + { + "title": "External", + "path": "/v1.13-docs/configuration/external.md" + }, + { + "title": "ACME", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/configuration/acme/README.md" + }, + { + "title": "HTTP01", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/configuration/acme/http01/README.md" + }, + { + "title": "External Load Balancer", + "path": "/v1.13-docs/configuration/acme/http01/externalloadbalancer.md" + } + ] + }, + { + "title": "DNS01", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/configuration/acme/dns01/README.md" + }, + { + "title": "ACMEDNS", + "path": "/v1.13-docs/configuration/acme/dns01/acme-dns.md" + }, + { + "title": "Akamai", + "path": "/v1.13-docs/configuration/acme/dns01/akamai.md" + }, + { + "title": "AzureDNS", + "path": "/v1.13-docs/configuration/acme/dns01/azuredns.md" + }, + { + "title": "Cloudflare", + "path": "/v1.13-docs/configuration/acme/dns01/cloudflare.md" + }, + { + "title": "DigitalOcean", + "path": "/v1.13-docs/configuration/acme/dns01/digitalocean.md" + }, + { + "title": "Google CloudDNS", + "path": "/v1.13-docs/configuration/acme/dns01/google.md" + }, + { + "title": "RFC-2136", + "path": "/v1.13-docs/configuration/acme/dns01/rfc2136.md" + }, + { + "title": "Route53", + "path": "/v1.13-docs/configuration/acme/dns01/route53.md" + }, + { + "title": "Webhook", + "path": "/v1.13-docs/configuration/acme/dns01/webhook.md" + } + ] + } + ] + } + ] + }, + { + "title": "Requesting Certificates", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/usage/README.md" + }, + { + "title": "Certificate Resources", + "path": "/v1.13-docs/usage/certificate.md" + }, + { + "title": "Prometheus Metrics", + "path": "/v1.13-docs/usage/prometheus-metrics.md" + }, + { + "title": "Securing Ingress Resources", + "path": "/v1.13-docs/usage/ingress.md" + }, + { + "title": "Securing Gateway Resources", + "path": "/v1.13-docs/usage/gateway.md" + }, + { + "title": "Securing Istio Service Mesh", + "path": "/v1.13-docs/usage/istio.md" + }, + { + "title": "CSI Driver", + "path": "/v1.13-docs/usage/csi.md" + }, + { + "title": "Kubernetes CertificateSigningRequests", + "path": "/v1.13-docs/usage/kube-csr.md" + }, + { + "title": "Policy for cert-manager certificates", + "path": "/v1.13-docs/usage/approver-policy.md" + } + ] + }, + { + "title": "Projects", + "routes": [ + { + "title": "Contents", + "path": "/v1.13-docs/projects/README.md" + }, + { + "title": "istio-csr", + "path": "/v1.13-docs/projects/istio-csr.md" + }, + { + "title": "csi-driver", + "path": "/v1.13-docs/projects/csi-driver.md" + }, + { + "title": "csi-driver-spiffe", + "path": "/v1.13-docs/projects/csi-driver-spiffe.md" + }, + { + "title": "approver-policy", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/projects/approver-policy/README.md" + }, + { + "title": "API Reference", + "path": "/v1.13-docs/projects/approver-policy/api-reference.md" + } + ] + }, + { + "title": "trust-manager", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/projects/trust-manager/README.md" + }, + { + "title": "API Reference", + "path": "/v1.13-docs/projects/trust-manager/api-reference.md" + } + ] + } + ] + }, + { + "title": "Tutorials", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/tutorials/README.md" + }, + { + "title": "Securing NGINX-ingress", + "path": "/v1.13-docs/tutorials/acme/nginx-ingress.md" + }, + { + "title": "GKE + Ingress + Let's Encrypt", + "path": "/v1.13-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md" + }, + { + "title": "AKS + LoadBalancer + Let's Encrypt", + "path": "/v1.13-docs/tutorials/getting-started-aks-letsencrypt/README.md" + }, + { + "title": "Migrating from Kube-LEGO", + "path": "/v1.13-docs/tutorials/acme/migrating-from-kube-lego.md" + }, + { + "title": "Backup and Restore Resources", + "path": "/v1.13-docs/tutorials/backup.md" + }, + { + "title": "DNS Validation", + "path": "/v1.13-docs/tutorials/acme/dns-validation.md" + }, + { + "title": "HTTP Validation", + "path": "/v1.13-docs/tutorials/acme/http-validation.md" + }, + { + "title": "Pomerium Ingress", + "path": "/v1.13-docs/tutorials/acme/pomerium-ingress.md" + }, + { + "title": "EKS + Ingress + Venafi", + "path": "/v1.13-docs/tutorials/venafi/venafi.md" + }, + { + "title": "Securing the istio Service Mesh using cert-manager", + "path": "/v1.13-docs/tutorials/istio-csr/istio-csr.md" + }, + { + "title": "Syncing Secrets Across Namespaces", + "path": "/v1.13-docs/tutorials/syncing-secrets-across-namespaces.md" + }, + { + "title": "Securing Ingresses with ZeroSSL", + "path": "/v1.13-docs/tutorials/zerossl/zerossl.md" + }, + { + "title": "Managing public trust in kubernetes with trust-manager", + "path": "/v1.13-docs/tutorials/getting-started-with-trust-manager/README.md" + } + ] + }, + { + "title": "Troubleshooting", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/troubleshooting/README.md" + }, + { + "title": "Troubleshooting ACME / Let's Encrypt Certificates", + "path": "/v1.13-docs/troubleshooting/acme.md" + }, + { + "title": "Troubleshooting webhook", + "path": "/v1.13-docs/troubleshooting/webhook.md" + } + ] + }, + { + "title": "FAQ", + "path": "/v1.13-docs/faq/README.md" + }, + { + "title": "Contributing", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/contributing/README.md" + }, + { + "title": "Feature Policy", + "path": "/v1.13-docs/contributing/policy.md" + }, + { + "title": "Building cert-manager", + "path": "/v1.13-docs/contributing/building.md" + }, + { + "title": "Contributing Flow", + "path": "/v1.13-docs/contributing/contributing-flow.md" + }, + { + "title": "CRDs", + "path": "/v1.13-docs/contributing/crds.md" + }, + { + "title": "DNS Providers", + "path": "/v1.13-docs/contributing/dns-providers.md" + }, + { + "title": "Running End-to-End Tests", + "path": "/v1.13-docs/contributing/e2e.md" + }, + { + "title": "Implementing External Issuers", + "path": "/v1.13-docs/contributing/external-issuers.md" + }, + { + "title": "DCO Sign Off", + "path": "/v1.13-docs/contributing/sign-off.md" + }, + { + "title": "Release Process", + "path": "/v1.13-docs/contributing/release-process.md" + }, + { + "title": "Developing with Kind", + "path": "/v1.13-docs/contributing/kind.md" + }, + { + "title": "Implementing Feature Gates", + "path": "/v1.13-docs/contributing/featuregates.md" + }, + { + "title": "Google Season of Docs", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/contributing/google-season-of-v1.13-docs/README.md" + }, + { + "title": "2022", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/contributing/google-season-of-v1.13-docs/2022/README.md" + }, + { + "title": "Improve the Navigation and Structure of the cert-manager Website", + "path": "/v1.13-docs/contributing/google-season-of-v1.13-docs/2022/improve-navigation-and-structure.md" + } + ] + } + ] + }, + { + "title": "Reporting Security Issues", + "path": "/v1.13-docs/contributing/security.md" + }, + { + "title": "Coding Conventions", + "path": "/v1.13-docs/contributing/coding-conventions.md" + }, + { + "title": "Third Party Code Donations", + "path": "/v1.13-docs/contributing/third-party-code-donation.md" + }, + { + "title": "Signing Keys", + "path": "/v1.13-docs/contributing/signing-keys.md" + }, + { + "title": "Importing cert-manager in Go", + "path": "/v1.13-docs/contributing/importing.md" + } + ] + }, + { + "title": "Concepts", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/concepts/README.md" + }, + { + "title": "Issuer", + "path": "/v1.13-docs/concepts/issuer.md" + }, + { + "title": "Certificate", + "path": "/v1.13-docs/concepts/certificate.md" + }, + { + "title": "CertificateRequest", + "path": "/v1.13-docs/concepts/certificaterequest.md" + }, + { + "title": "ACME Orders and Challenges", + "path": "/v1.13-docs/concepts/acme-orders-challenges.md" + }, + { + "title": "Webhook", + "path": "/v1.13-docs/concepts/webhook.md" + }, + { + "title": "CA Injector", + "path": "/v1.13-docs/concepts/ca-injector.md" + } + ] + }, + { + "title": "Reference", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/reference/README.md" + }, + { + "title": "Command Line Tool (cmctl)", + "path": "/v1.13-docs/reference/cmctl.md" + }, + { + "title": "TLS Terminology", + "path": "/v1.13-docs/reference/tls-terminology.md" + }, + { + "title": "Components / Docker Images", + "routes": [ + { + "title": "Introduction", + "path": "/v1.13-docs/cli/README.md" + }, + { + "title": "acmesolver", + "path": "/v1.13-docs/cli/acmesolver.md" + }, + { + "title": "cainjector", + "path": "/v1.13-docs/cli/cainjector.md" + }, + { + "title": "cmctl", + "path": "/v1.13-docs/cli/cmctl.md" + }, + { + "title": "controller", + "path": "/v1.13-docs/cli/controller.md" + }, + { + "title": "webhook", + "path": "/v1.13-docs/cli/webhook.md" + } + ] + }, + { + "title": "API Reference", + "path": "/v1.13-docs/reference/api-v1.13-docs.md" + } + ] + } + ] + } + ] +} diff --git a/content/v1.13-docs/projects/README.md b/content/v1.13-docs/projects/README.md new file mode 100644 index 0000000000..185df2fce4 --- /dev/null +++ b/content/v1.13-docs/projects/README.md @@ -0,0 +1,31 @@ +--- +title: Projects +description: 'Satellite Projects of cert-manager' +--- + +The cert-manager project has a number of [satellite projects](https://github.com/cert-manager) +that extend the project's functionality, and complement the core cert-manager feature-set. + +These tools help with security, compliance and control. + +- [istio-csr](./istio-csr.md): Secure Istio service mesh with istio-csr which is + an agent that allows for [Istio](https://istio.io) workload and control plane + components to be secured using cert-manager. +- [approver-policy](./approver-policy/README.md): + a cert-manager **approver** that will automatically approve or deny + certificate requests based on defined policy. +- [csi-driver](./csi-driver.md): + a Container Storage Interface (CSI) driver plugin for Kubernetes to work along + cert-manager. The goal for this plugin is to seamlessly request and mount + certificate key pairs to pods. This is useful for facilitating mTLS, or + otherwise securing connections of pods with guaranteed present certificates + whilst having all of the features that cert-manager provides. +- [csi-driver-spiffe](./csi-driver-spiffe.md): + another CSI driver plugin to work along cert-manager. This CSI driver + transparently delivers [SPIFFE](https://spiffe.io/) + [SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-verifiable-identity-document-svid) + in the form of X.509 certificate key pairs to mounting Kubernetes Pods. The + end result is all and any Pod running in Kubernetes can securely request their + SPIFFE identity document from a Trust Domain with minimal configuration. +- [trust-manager](./trust-manager/README.md): the easiest way to manage TLS trust bundles in Kubernetes and OpenShift clusters. +- [trust-manager API reference](./trust-manager/api-reference.md): full documentation of the trust-manager CRD(s) diff --git a/content/v1.13-docs/projects/approver-policy/README.md b/content/v1.13-docs/projects/approver-policy/README.md new file mode 100644 index 0000000000..bdad17d829 --- /dev/null +++ b/content/v1.13-docs/projects/approver-policy/README.md @@ -0,0 +1,431 @@ +--- +title: approver-policy +description: 'Policy plugin for cert-manager' +--- + +approver-policy is a cert-manager +[approver](../../concepts/certificaterequest.md#approval) +that will approve or deny CertificateRequests based on policies defined in +the `CertificateRequestPolicy` custom resource. + +## Prerequisites + +[cert-manager must be installed](../../installation/README.md), and +the [the default approver in cert-manager must be disabled](../../concepts/certificaterequest.md#approver-controller). + +> ⚠️ If the default approver is not disabled in cert-manager, approver-policy will +> race with cert-manager and policy will be ineffective. + +If you install cert-manager using `helm install` or `helm upgrade`, +you can disable the default approver by [Customizing the Chart Before Installing](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing) using the `--set` or `--values` command line flags: + +``` +# Example --set value +--set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver +``` + +```yaml +# Example --values file content +extraArgs: + - "--controllers=*,-certificaterequests-approver" # ⚠ Disable cert-manager's built-in approver +``` + +Here's a full example which will install cert-manager or reconfigure it if it is already installed: + +```terminal +helm upgrade cert-manager jetstack/cert-manager \ + --install \ + --create-namespace \ + --namespace cert-manager \ + --version REPLACE-WITH-YOUR-CERT-MANAGER-VERSION \ + --set installCRDs=true \ + --set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver +``` + +> ℹ️ The `--set installCRDs=true` setting is a convenient way to install the +> cert-manager CRDS, but it is optional and has some drawbacks. +> Read [Helm: Installing Custom Resource Definitions](https://deploy-preview-1216--cert-manager-website.netlify.app/docs/installation/helm/#3-install-customresourcedefinitions) to learn more. +> +> ℹ️ Be sure to customize the cert-manager controller `extraArgs`, +> which are at the top level of the values file. +> *Do not* change the `webhook.extraArgs`, `startupAPICheck.extraArgs` or `cainjector.extraArgs` settings. +> +> ⚠️ If you are reconfiguring an already installed cert-manager, +> check whether the original installation already customized the `extraArgs` value +> by running `helm get values cert-manager --namespace cert-manager`. +> If there are already `extraArgs` values, merge those with the extra `--controllers` value. +> Otherwise your original `extraArgs` values will be overwritten. + +## Installation + +To install approver-policy: + +```terminal +helm repo add jetstack https://charts.jetstack.io --force-update +helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait +``` + +If you are using approver-policy with [external +issuers](../../configuration/external.md), you _must_ +include their signer names so that approver-policy has permissions to approve +and deny CertificateRequests that +[reference them](../../concepts/certificaterequest.md#rbac-syntax). +For example, if using approver-policy for the internal issuer types, along with +[google-cas-issuer](https://github.com/jetstack/google-cas-issuer), and +[aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer), +set the following values when installing: + +```terminal +$ helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait \ + --set app.approveSignerNames="{\ +issuers.cert-manager.io/*,clusterissuers.cert-manager.io/*,\ +googlecasclusterissuers.cas-issuer.jetstack.io/*,googlecasissuers.cas-issuer.jetstack.io/*,\ +awspcaclusterissuers.awspca.cert-manager.io/*,awspcaissuers.awspca.cert-manager.io/*\ +}" +``` + +## Configuration + +> Example policy resources can be found +> [here](https://github.com/cert-manager/approver-policy/tree/main/docs/examples). + +When a CertificateRequest is created, approver-policy will evaluate whether the +request is appropriate for any existing policy, and if so, evaluate whether it +should be approved or denied. + +For a CertificateRequest to be appropriate for a policy and therefore be +evaluated by it, it must be both bound via RBAC _and_ be selected by the policy +selector. CertificateRequestPolicy currently only supports `issuerRef` as a +selector. + +**If at least one policy permits the request, the request is approved. If at +least one policy is appropriate for the request but none of those permit the +request, the request is denied.** + +A denied CertificateRequest is considered to be permanently failed. If it was +created for a Certificate resource, the issuance will be retried with +[exponential backoff](../../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) +like all other permanent issuance failures. A CertificateRequest that is neither +approved nor denied (because no matching policy was found) will not be further +processed by cert-manager until it gets either approved or denied. + +CertificateRequestPolicies are cluster scoped resources that can be thought of +as "policy profiles". They describe any request that is approved by that +policy. Policies are bound to Kubernetes users and ServiceAccounts using RBAC. + +Below is an example of a policy that is bound to all Kubernetes users who may +only request certificates that have the common name of `"hello.world"`. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: test-policy +spec: + allowed: + commonName: + value: "hello.world" + required: true + selector: + # Select all IssuerRef + issuerRef: {} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-policy:hello-world +rules: + - apiGroups: ["policy.cert-manager.io"] + resources: ["certificaterequestpolicies"] + verbs: ["use"] + # Name of the CertificateRequestPolicies to be used. + resourceNames: ["test-policy"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-policy:hello-world +roleRef: +# ClusterRole or Role _must_ be bound to a user for the policy to be considered. + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-policy:hello-world +subjects: +# The users who should be bound to the policies defined. +# Note that in the case of users creating Certificate resources, cert-manager +# is the entity that is creating the actual CertificateRequests, and so the +# cert-manager controller's +# Service Account should be bound instead. +- kind: Group + name: system:authenticated + apiGroup: rbac.authorization.k8s.io +``` + +## Behavior + +CertificateRequestPolicy are split into 4 parts; `allowed`, `contraints`, +`selector`, and `plugins`. + +### Allowed + +Allowed is the block that defines attributes that match against the +corresponding attribute in the request. A request is permitted by the policy if +the request omits an allowed attribute, but will _deny_ the request if it +contains an attribute which is _not_ present in the allowed block. + +An allowed attribute can be marked as `required`, which if true, will enforce +that the attribute has been defined in the request. A field can only be marked +as `required` if the corresponding field is also defined. The `required` field +is not available for `isCA` or `usages`. + +In the following CertificateRequestPolicy, a request will be permitted if it +does not request a DNS name, requests the DNS name `"example.com"`, but will be +denied when requesting `"bar.example.com"`. + +```yaml +spec: + ... + allowed: + dnsNames: + values: + - "example.com" + - "foo.example.com" + ... +``` + +In the following, a request will be denied if the request contains no Common +Name, but will permit requests whose Common Name ends in ".com". + +```yaml +spec: + ... + allowed: + commonName: + value: "*.com" + required: true + ... +``` + +If an allowed field is omitted, that attribute is considered "deny all" for +requests. + +Allowed string fields accept wildcards "\*" within its values. Wildcards "\*" in +patterns represent any string that has a length of 0 or more. A pattern +containing only "\*" will match anything. A pattern containing `"\*foo"` will +match `"foo"` as well as any string which ends in `"foo"` (e.g. `"bar-foo"`). A +pattern containing `"\*.foo"` will match `"bar-123.foo"`, but not `"barfoo"`. + +Allowed fields that are lists will permit requests that are a subset of that +list. This means that if `usages` contains `["server auth", "client auth"]`, +then a request containing only `["server auth"]` would be permitted, but not +`["server auth", "cert sign"]`. + +Below is an example including all supported allowed fields of +CertificateRequestPolicy. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: my-policy +spec: + allowed: + commonName: + value: "example.com" + dnsNames: + values: + - "example.com" + - "*.example.com" + ipAddresses: + values: + - "1.2.3.4" + - "10.0.1.*" + uris: + values: + - "spiffe://example.org/ns/*/sa/*" + emailAddresses: + values: + - "*@example.com" + required: true + isCA: false + usages: + - "server auth" + - "client auth" + subject: + organizations: + values: ["hello-world"] + countries: + values: ["*"] + organizationalUnits: + values: ["*"] + localities: + values: ["*"] + provinces: + values: ["*"] + streetAddresses: + values: ["*"] + postalCodes: + values: ["*"] + serialNumber: + value: "*" + ... +``` + +### Constraints + +Constraints is the block that is used to limit what attributes the request can +have. If a constraint is not defined, then the attribute is considered "allow +all". + +Below is an example containing all supported constraints fields of +CertificateRequestPolicy. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: my-policy +spec: + ... + constraints: + minDuration: 1h + maxDuration: 24h + privateKey: + algorithm: RSA + minSize: 2048 + maxSize: 4096 + ... +``` + +### Selector + +Selector is a required field that is used for matching +CertificateRequestPolicies against CertificateRequests for evaluation. A +CertificateRequestPolicy must select, and therefore match, a CertificateRequest +for it to be considered for evaluation of the request. + +> ⚠️ Note that the user must still be bound by [RBAC](#configuration) for +> the policy to be considered for evaluation against a request. + +approver-policy supports selecting over the `issuerRef` and the `namespace` of a +request. + +At least either an `issuerRef` *or* `namespace` selector must be defined, even +if set to empty (`{}`). **Both** selectors must match on a CertificateRequest +for the request to evaluated by the policy if both are defined. + +#### `issuerRef` + +The `issuerRef` CertificateRequestPolicy selector selects on the corresponding +`issuerRef` stanza on the CertificateRequest. + +`issuerRef` values accept wildcards "\*". If an `issuerRef` is set to an empty +object `{}`, then the policy will match against _all_ requests. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: my-policy +spec: + ... + selector: + issuerRef: + name: "my-ca" + kind: "*Issuer" + group: "cert-manager.io" +``` + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: match-all-requests +spec: + ... + selector: + issuerRef: {} +``` + +#### `namespace` + +The `namespace` CertificateRequestPolicy selector selects on the Namespace to +which the CertificateRequest was created in. The selector can be defined with +either `matchNames` or `matchLabels`. + +`matchNames` takes a list of strings which match the _name_ of the Namespace. +Accepts wildcards "\*". + +`matchLabels` takes a list of key value strings which match on the labels of the +Namespace that the CertificateRequest was created in. Please see the [Kubernetes +documentation][] for more information on `matchLabels` behavior. + +If a `namespace` is set to an empty object `{}`, then the policy will match +against _all_ requests. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: my-policy +spec: + ... + selector: + namespace: + matchNames: + - "default" + - "app-team-*" + matchLabels: + foo: bar + team: dev +``` + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: match-all-requests +spec: + ... + selector: + namespace: {} +``` + +[Kubernetes documentation]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#resources-that-support-set-based-requirements + +### Plugins + +Plugins are external approvers that are built into approver-policy at compile +time. Plugins are designed to be used as extensions to the existing policy +checks where the user requires special functionality that the existing checks +can't provide. + +Plugins are defined as a block on the CertificateRequestPolicy `spec`. + +```yaml +apiVersion: policy.cert-manager.io/v1alpha1 +kind: CertificateRequestPolicy +metadata: + name: plugins +spec: + ... + plugins: + my-plugin: + values: + val-1: key-1 +``` + +## Known Plugins from the Community + +- [CEL approver-policy plugin](https://github.com/erikgb/cel-approver-policy-plugin) (experimental) + +If you want to implement an external approver policy plugin take a look at the +example implementation at +https://github.com/cert-manager/example-approver-policy-plugin. + +Have you implemented a plugin for approver-policy? Feel free to add a link to your plugin from this page by +opening a pull request in the [cert-manager website project](https://github.com/cert-manager/website). + +## API Reference + +> 📖 Read the [approver-policy API reference](api-reference.md). diff --git a/content/v1.13-docs/projects/approver-policy/api-reference.md b/content/v1.13-docs/projects/approver-policy/api-reference.md new file mode 100644 index 0000000000..e4d45bb829 --- /dev/null +++ b/content/v1.13-docs/projects/approver-policy/api-reference.md @@ -0,0 +1,978 @@ +--- +title: approver-policy API Reference +description: "approver-policy API documentation" +--- + +Packages: + +- [`policy.cert-manager.io/v1alpha1`](#policycert-manageriov1alpha1) + +# `policy.cert-manager.io/v1alpha1` + +Resource Types: + + +- [CertificateRequestPolicy](#certificaterequestpolicy) + + + + +## `CertificateRequestPolicy` + + + + + +CertificateRequestPolicy is an object for describing a "policy profile" that makes decisions on whether applicable CertificateRequests should be approved or denied. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        apiVersionstringpolicy.cert-manager.io/v1alpha1true
        kindstringCertificateRequestPolicytrue
        metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
        specobject + CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy.
        +
        false
        statusobject + CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy.
        +
        false
        + + +### `CertificateRequestPolicy.spec` + + +CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        selectorobject + Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation.
        +
        true
        allowedobject + Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible.
        +
        false
        constraintsobject + Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute.
        +
        false
        pluginsmap[string]object + Plugins define a set of plugins and their configuration that should be executed when this policy is evaluated against a CertificateRequest. A plugin must already be built within approver-policy for it to be available.
        +
        false
        + + +### `CertificateRequestPolicy.spec.selector` + + +Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        issuerRefobject + IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". + The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ```
        +
        false
        namespaceobject + Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected.
        +
        false
        + + +### `CertificateRequestPolicy.spec.selector.issuerRef` + + +IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". + The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ``` + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        groupstring + Group is the wildcard selector to match the `spec.issuerRef.group` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
        +
        false
        kindstring + Kind is the wildcard selector to match the `spec.issuerRef.kind` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
        +
        false
        namestring + Name is the wildcard selector to match the `spec.issuerRef.name` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
        +
        false
        + + +### `CertificateRequestPolicy.spec.selector.namespace` + + +Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        matchLabelsmap[string]string + MatchLabels is the set of Namespace labels that select on CertificateRequests which have been created in a Namespace matching the selector.
        +
        false
        matchNames[]string + MatchNames are the set of Namespace names that select on CertificateRequests that have been created in a matching Namespace. Accepts wildcards "*".
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed` + + +Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        commonNameobject + CommonName defines the X.509 Common Name that is permissible.
        +
        false
        dnsNamesobject + DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*".
        +
        false
        emailAddressesobject + EmailAddresses defines the X.509 Email SANs that may be requested for.
        +
        false
        ipAddressesobject + IPAddresses defines the X.509 IP SANs that may be requested for.
        +
        false
        isCAboolean + IsCA defines whether it is permissible for a CertificateRequest to have the `spec.IsCA` field set to `true`. An omitted field, value of `nil` or `false`, forbids the `spec.IsCA` field from bring `true`. A value of `true` permits CertificateRequests setting the `spec.IsCA` field to `true`.
        +
        false
        subjectobject + Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested.
        +
        false
        urisobject + URIs defines the X.509 URI SANs that may be requested for.
        +
        false
        usages[]enum + Usages defines the list of permissible key usages that may appear on the CertificateRequest `spec.keyUsages` field. An omitted field or value of `nil` forbids any Usages being requested. An empty slice `[]` is equivalent to `nil`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.commonName` + + +CommonName defines the X.509 Common Name that is permissible. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
        +
        false
        valuestring + Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.dnsNames` + + +DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*". + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.emailAddresses` + + +EmailAddresses defines the X.509 Email SANs that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.ipAddresses` + + +IPAddresses defines the X.509 IP SANs that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.subject` + + +Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        countriesobject + Countries define the X.509 Subject Countries that may be requested for.
        +
        false
        localitiesobject + Localities defines the X.509 Subject Localities that may be requested for.
        +
        false
        organizationalUnitsobject + OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for.
        +
        false
        organizationsobject + Organizations define the X.509 Subject Organizations that may be requested for.
        +
        false
        postalCodesobject + PostalCodes defines the X.509 Subject Postal Codes that may be requested for.
        +
        false
        provincesobject + Provinces defines the X.509 Subject Provinces that may be requested for.
        +
        false
        serialNumberobject + SerialNumber defines the X.509 Subject Serial Number that may be requested for.
        +
        false
        streetAddressesobject + StreetAddresses defines the X.509 Subject Street Addresses that may be requested for.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.subject.countries` + + +Countries define the X.509 Subject Countries that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.subject.localities` + + +Localities defines the X.509 Subject Localities that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.subject.organizationalUnits` + + +OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.subject.organizations` + + +Organizations define the X.509 Subject Organizations that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.subject.postalCodes` + + +PostalCodes defines the X.509 Subject Postal Codes that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.subject.provinces` + + +Provinces defines the X.509 Subject Provinces that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.subject.serialNumber` + + +SerialNumber defines the X.509 Subject Serial Number that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
        +
        false
        valuestring + Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.subject.streetAddresses` + + +StreetAddresses defines the X.509 Subject Street Addresses that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.allowed.uris` + + +URIs defines the X.509 URI SANs that may be requested for. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        requiredboolean + Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
        +
        false
        values[]string + Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
        +
        false
        + + +### `CertificateRequestPolicy.spec.constraints` + + +Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute. + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        maxDurationstring + MaxDuration defines the maximum duration a certificate may be requested for. Values are inclusive (i.e. a max value of `1h` will accept a duration of `1h`). MaxDuration and MinDuration may be the same value. An omitted field or value of `nil` permits any maximum duration. If MaxDuration is defined, a duration _must_ be requested on the CertificateRequest.
        +
        false
        minDurationstring + MinDuration defines the minimum duration a certificate may be requested for. Values are inclusive (i.e. a min value of `1h` will accept a duration of `1h`). MinDuration and MaxDuration may be the same value. An omitted field or value of `nil` permits any minimum duration. If MinDuration is defined, a duration _must_ be requested on the CertificateRequest.
        +
        false
        privateKeyobject + PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor.
        +
        false
        + + +### `CertificateRequestPolicy.spec.constraints.privateKey` + + +PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor. + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        algorithmenum + Algorithm defines the allowed crypto algorithm that is used by the requestor for their private key in their request. An omitted field or value of `nil` permits any Algorithm.
        +
        + Enum: RSA, ECDSA, Ed25519
        +
        false
        maxSizeinteger + MaxSize defines the maximum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MaxSize and MinSize may be the same value. An omitted field or value of `nil` permits any maximum size.
        +
        false
        minSizeinteger + MinSize defines the minimum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MinSize and MaxSize may be the same value. An omitted field or value of `nil` permits any minimum size.
        +
        false
        + + +### `CertificateRequestPolicy.spec.plugins[key]` + + +CertificateRequestPolicyPluginData is configuration needed by the plugin approver to evaluate a CertificateRequest on this policy. + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        valuesmap[string]string + Values define a set of well-known, to the plugin, key value pairs that are required for the plugin to successfully evaluate a request based on this policy.
        +
        false
        + + +### `CertificateRequestPolicy.status` + + +CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy. + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        conditions[]object + List of status conditions to indicate the status of the CertificateRequestPolicy. Known condition types are `Ready`.
        +
        false
        + + +### `CertificateRequestPolicy.status.conditions[index]` + + +CertificateRequestPolicyCondition contains condition information for a CertificateRequestPolicyStatus. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        statusstring + Status of the condition, one of ('True', 'False', 'Unknown').
        +
        true
        typestring + Type of the condition, known values are (`Ready`).
        +
        true
        lastTransitionTimestring + LastTransitionTime is the timestamp corresponding to the last status change of this condition.
        +
        + Format: date-time
        +
        false
        messagestring + Message is a human readable description of the details of the last transition, complementing reason.
        +
        false
        observedGenerationinteger + If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the CertificateRequestPolicy.
        +
        + Format: int64
        +
        false
        reasonstring + Reason is a brief machine readable explanation for the condition's last transition.
        +
        false
        diff --git a/content/v1.13-docs/projects/csi-driver-spiffe.md b/content/v1.13-docs/projects/csi-driver-spiffe.md new file mode 100644 index 0000000000..771ce6a1ae --- /dev/null +++ b/content/v1.13-docs/projects/csi-driver-spiffe.md @@ -0,0 +1,257 @@ +--- +title: csi-driver-spiffe +description: 'Container Storage Interface (CSI) driver plugin for Kubernetes, providing SPIFFE SVIDs using cert-manager' +--- + +csi-driver-spiffe is a Container Storage Interface (CSI) driver plugin for +Kubernetes, designed to work alongside [cert-manager](https://cert-manager.io/). + +It transparently delivers [SPIFFE](https://spiffe.io/) [SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-verifiable-identity-document-svid) +(in the form of X.509 certificate key pairs) to mounting Kubernetes Pods. + +The end result is that any and all Pods running in Kubernetes can securely request +a SPIFFE identity document from a Trust Domain with minimal configuration. + +These documents in turn have the following properties: + +- automatically renewed ✔️ +- private key never leaves the node's virtual memory ✔️ +- each Pod's document is unique ✔️ +- the document shares the same life cycle as the Pod and is destroyed on Pod termination ✔️ + +```yaml +... + volumeMounts: + - mountPath: "/var/run/secrets/spiffe.io" + name: spiffe + volumes: + - name: spiffe + csi: + driver: spiffe.csi.cert-manager.io + readOnly: true +``` + +SPIFFE documents can then be used by Pods for mutual TLS (mTLS) or other authentication within their Trust Domain. +### Components + +The project is split into two components. + +#### CSI Driver + +The CSI driver runs as DaemonSet on the cluster which is responsible for +generating, requesting, and mounting the certificate key pair to Pods on the +node it manages. The CSI driver creates and manages a +[tmpfs](https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html) directory +which is used to create and mount Pod volumes from. + +When a Pod is created with the CSI volume configured, the +driver will locally generate a private key, and create a cert-manager +[CertificateRequest](../concepts/certificaterequest.md) +in the same Namespace as the Pod. + +The driver uses [CSI Token Request](https://kubernetes-csi.github.io/docs/token-requests.html) to both +discover the Pod's identity to form the SPIFFE identity contained in the X.509 +certificate signing request, as well as securely impersonate its ServiceAccount +when creating the CertificateRequest. + +Once signed by the pre-configured target signer, the driver will mount the +private key and signed certificate into the Pod's Volume to be made available as +a Volume Mount. This certificate key pair is regularly renewed based on the +expiry of the signed certificate. + +#### Approver + +A distinct [cert-manager approver](../concepts/certificaterequest.md#approval) +Deployment is responsible for managing the approval and denial condition of +created CertificateRequests that target the configured SPIFFE Trust Domain +signer. + +The approver ensures that requests have: + +1. acceptable key usages (Key Encipherment, Digital Signature, Client Auth, Server Auth); +2. a requested duration which matches the enforced duration (default 1 hour); +3. no [SANs](https://en.wikipedia.org/wiki/Subject_Alternative_Name) or other + identifiable attributes except a single [URI SAN](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier); +4. a URI SAN which is the SPIFFE identity of the ServiceAccount which created + the CertificateRequest; +5. a SPIFFE ID Trust Domain matching the one that was configured at startup. + +If any of these checks do not pass, the CertificateRequest will be marked as +Denied, else it will be marked as Approved. The approver will only manage +CertificateRequests who request from the same [IssuerRef](../concepts/certificaterequest.md) +that has been configured. + +## Installation + +### Requirements + +csi-driver-spiffe generally requires Kubernetes version `v1.21` or newer. + +If running on Kubernetes `v1.20`, you'll need the `--feature-gates=CSIServiceAccountToken=true` flag. + +cert-manager `v1.3` or higher is also required. + +### Steps + +#### 1. Install cert-manager + +csi-driver-spiffe requires cert-manager to be [installed](../installation/README.md) but +a default installation of cert-manager **will not work**. + +> ⚠️ It is **vital** that the [default approver is disabled in cert-manager](../concepts/certificaterequest.md#approver-controller) ⚠️ + +If the default approver is not disabled, the csi-driver-spiffe approver will +race with cert-manager and policy enforcement will become useless. + +```bash +helm repo add jetstack https://charts.jetstack.io --force-update + +# NOTE: This isn't the usual cert-manager install process; +# we're disabling the cert-manager approver. +# See explanation above! + +helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager \ + --set extraArgs={--controllers='*\,-certificaterequests-approver'} \ + --set installCRDs=true \ + --create-namespace +``` + +#### 2. Configure an Issuer / ClusterIssuer + +Install or configure a [ClusterIssuer](../configuration/README.md) to give +cert-manager the ability to sign against your Trust Domain. + +If you want a namespace-scoped Issuer, then it must be created in every namespace +that Pods will mount volumes from. + +You must use an Issuer type which is compatible with signing URI SAN certificates; +ACME issuers won't generally work, and the SelfSigned issuer is not appropriate. + +An example demo [ClusterIssuer](../concepts/issuer.md#namespaces) can +be found [in the csi-driver-spiffe repo](https://github.com/cert-manager/csi-driver-spiffe/blob/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/clusterissuer.yaml). + +> ⚠️ This Trust Domain's root CA is generated by cert-manager and **the private key is stored in the cluster** +> This might not be appropriate for production deployments! + +We'll also use [cmctl](../reference/cmctl.md) to approve the CertificateRequest, +since the default approver was disabled above. + +```terminal +kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/clusterissuer.yaml + +# We must also approve the CertificateRequest since we +# disabled the default approver +cmctl approve -n cert-manager \ + $(kubectl get cr -n cert-manager -ojsonpath='{.items[0].metadata.name}') +``` + +#### 3. Install csi-driver-spiffe + +Install csi-driver-spiffe into the cluster using the issuer we configured. We +must also configure the issuer resource type and name of the issuer we +configured so that the approver has [permissions to approve referencing CertificateRequests](../concepts/certificaterequest.md#rbac-syntax). + +Note that the `issuer.name`, `issuer.kind` and `issuer.group` will need to be changed to match +the issuer you're actually using! + +```bash +helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \ + --set "app.logLevel=1" \ + --set "app.trustDomain=my.trust.domain" \ + --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \ + \ + --set "app.issuer.name=csi-driver-spiffe-ca" \ + --set "app.issuer.kind=ClusterIssuer" \ + --set "app.issuer.group=cert-manager.io" +``` + +## Usage + +Once the driver is successfully installed, Pods can begin to request and mount +their key and SPIFFE certificate. Since the Pod's ServiceAccount is impersonated +when creating CertificateRequests, every ServiceAccount must be given that +permission which intends to use the volume. + +Example manifest with a dummy Deployment: + +```bash +kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/example-app.yaml + +kubectl exec -n sandbox \ + $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') \ + -- \ + cat /var/run/secrets/spiffe.io/tls.crt | \ + openssl x509 --noout --text | \ + grep "Issuer:" +# expected output: Issuer: CN = csi-driver-spiffe-ca + +kubectl exec -n sandbox \ + $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') \ + -- \ + cat /var/run/secrets/spiffe.io/tls.crt | \ + openssl x509 --noout --text | \ + grep "URI:" +# expected output: URI:spiffe://foo.bar/ns/sandbox/sa/example-app +``` + +### FS-Group + +When running Pods with a specified user or group, the volume will not be +readable by default due to Unix based file system permissions. The mounting +volumes file group can be specified using the following volume attribute: + +```yaml +... + securityContext: + runAsUser: 123 + runAsGroup: 456 + volumes: + - name: spiffe + csi: + driver: spiffe.csi.cert-manager.io + readOnly: true + volumeAttributes: + spiffe.csi.cert-manager.io/fs-group: "456" +``` + +```bash +kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/fs-group-app.yaml + +kubectl exec -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app-fs-group -o jsonpath='{.items[0].metadata.name}') -- cat /var/run/secrets/spiffe.io/tls.crt | openssl x509 --noout --text | grep URI: +# expected output: URI:spiffe://foo.bar/ns/sandbox/sa/fs-group-app +``` + +### Root CA Bundle + +By default, the CSI driver will only mount the Pod's private key and signed +certificate. csi-driver-spiffe can be optionally configured to also mount a +statically defined CA bundle from a volume that will be written to all Pod +volumes. + +If the CSI driver detects this bundle has changed (through overwrite, renewal, +etc), the new bundle will be written to all existing volumes. + +The following example mounts the CA certificate used by the Trust Domain +ClusterIssuer. + +```terminal +helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \ + --set "app.logLevel=1" \ + --set "app.trustDomain=my.trust.domain" \ + --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \ + \ + --set "app.issuer.name=csi-driver-spiffe-ca" \ + --set "app.issuer.kind=ClusterIssuer" \ + --set "app.issuer.group=cert-manager.io" \ + \ + --set "app.driver.volumes[0].name=root-cas" \ + --set "app.driver.volumes[0].secret.secretName=csi-driver-spiffe-ca" \ + --set "app.driver.volumeMounts[0].name=root-cas" \ + --set "app.driver.volumeMounts[0].mountPath=/var/run/secrets/cert-manager-csi-driver-spiffe" \ + --set "app.driver.sourceCABundle=/var/run/secrets/cert-manager-csi-driver-spiffe/ca.crt" + +kubectl rollout restart deployment -n sandbox my-csi-app + +kubectl exec -it -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') -- ls /var/run/secrets/spiffe.io/ +# expected output: ca.crt tls.crt tls.key +``` diff --git a/content/v1.13-docs/projects/csi-driver.md b/content/v1.13-docs/projects/csi-driver.md new file mode 100644 index 0000000000..31f7f1c74d --- /dev/null +++ b/content/v1.13-docs/projects/csi-driver.md @@ -0,0 +1,231 @@ +--- +title: csi-driver +description: '' +--- + +csi-driver is a Container Storage Interface (CSI) driver plugin for Kubernetes +to work along cert-manager. The goal for this plugin is to seamlessly request +and mount certificate key pairs to pods. This is useful for facilitating mTLS, +or otherwise securing connections of pods with guaranteed present certificates +whilst having all of the features that cert-manager provides. + +## Why a CSI Driver? + +- Ensure private keys never leave the node and are never sent over the network. + All private keys are stored locally on the node. +- Unique key and certificate per application replica with a grantee to be + present on application run time. +- Reduce resource management overhead by defining certificate request spec + in-line of the Kubernetes Pod template. +- Automatic renewal of certificates based on expiry of each individual + certificate. +- Keys and certificates are destroyed during application termination. +- Scope for extending plugin behavior with visibility on each replica's + certificate request and termination. + +## Requirements and Installation + +This CSI driver plugin makes use of the 'CSI inline volume' feature - Alpha as +of `v1.15` and beta in `v1.16`. Kubernetes versions `v1.16` and higher require +no extra configuration however `v1.15` requires the following feature gate set: +``` +--feature-gates=CSIInlineVolume=true +``` + +You must have a working installation of cert-manager present on the cluster. +Instructions on how to install cert-manager can be found +[on cert-manager.io](../installation/README.md). + +To install the csi-driver, use helm install: + +```terminal +helm repo add jetstack https://charts.jetstack.io --force-update +helm upgrade -i -n cert-manager cert-manager-csi-driver jetstack/cert-manager-csi-driver --wait +``` + +Or apply the static manifests to your cluster: + +```terminal +helm repo add jetstack https://charts.jetstack.io --force-update +helm template jetstack/cert-manager-csi-driver | kubectl apply -n cert-manager -f - +``` + + +You can verify the installation has completed correctly by checking the presence +of the CSIDriver resource as well as a CSINode resource present for each node, +referencing `csi.cert-manager.io`. + +``` +$ kubectl get csidrivers +NAME CREATED AT +csi.cert-manager.io 2019-09-06T16:55:19Z + +$ kubectl get csinodes -o yaml +apiVersion: v1 +items: +- apiVersion: storage.k8s.io/v1beta1 + kind: CSINode + metadata: + name: kind-control-plane + ownerReferences: + - apiVersion: v1 + kind: Node + name: kind-control-plane +... + spec: + drivers: + - name: csi.cert-manager.io + nodeID: kind-control-plane + topologyKeys: null +... +``` + +The CSI driver is now installed and is ready to be used for pods in the cluster. + +## Requesting and Mounting Certificates + +To request certificates from cert-manager, simply define a volume mount where +the key and certificate will be written to, along with a volume with attributes +that define the cert-manager request. The following is a dummy app that mounts a +key certificate pair to `/tls` and has been signed by the `ca-issuer` with a DNS +name valid for `my-service.sandbox.svc.cluster.local`. + +``` +apiVersion: v1 +kind: Pod +metadata: + name: my-csi-app + namespace: sandbox + labels: + app: my-csi-app +spec: + containers: + - name: my-frontend + image: busybox + volumeMounts: + - mountPath: "/tls" + name: tls + command: [ "sleep", "1000000" ] + volumes: + - name: tls + csi: + driver: csi.cert-manager.io + volumeAttributes: + csi.cert-manager.io/issuer-name: ca-issuer + csi.cert-manager.io/dns-names: ${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local +``` + +Once created, the CSI driver will generate a private key locally, request a +certificate from cert-manager based on the given attributes, then store both +locally to be mounted to the pod. The pod will remain in a pending state until +this process has been completed. + +For more information on how to set up issuers for your cluster, refer to the +cert-manager documentation +[here](../configuration/README.md). **Note** it is not +possible to use `SelfSigned` Issuers with the CSI Driver. In order for +cert-manager to self sign a certificate, it needs access to the secret +containing the private key that signed the certificate request to sign the end +certificate. This secret is not used and so not available in the CSI driver use +case. + +## Supported Volume Attributes + +The csi-driver driver aims to have complete feature parity with all possible +values available through the cert-manager API however currently supports the +following values; + +| Attribute | Description | Default | Example | +|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|----------------------------------| +| `csi.cert-manager.io/issuer-name` | The Issuer name to sign the certificate request. | | `ca-issuer` | +| `csi.cert-manager.io/issuer-kind` | The Issuer kind to sign the certificate request. | `Issuer` | `ClusterIssuer` | +| `csi.cert-manager.io/issuer-group` | The group name the Issuer belongs to. | `cert-manager.io` | `out.of.tree.foo` | +| `csi.cert-manager.io/common-name` | Certificate common name (supports variables). | | `my-cert.foo` | +| `csi.cert-manager.io/dns-names` | DNS names the certificate will be requested for. At least a DNS Name, IP or URI name must be present (supports variables). | | `a.b.foo.com,c.d.foo.com` | +| `csi.cert-manager.io/ip-sans` | IP addresses the certificate will be requested for. | | `192.0.0.1,192.0.0.2` | +| `csi.cert-manager.io/uri-sans` | URI names the certificate will be requested for (supports variables). | | `spiffe://foo.bar.cluster.local` | +| `csi.cert-manager.io/duration` | Requested duration the signed certificate will be valid for. | `720h` | `1880h` | +| `csi.cert-manager.io/is-ca` | Mark the certificate as a certificate authority. | `false` | `true` | +| `csi.cert-manager.io/key-usages` | Set the key usages on the certificate request. | `digital signature,key encipherment` | `server auth,client auth` | +| `csi.cert-manager.io/key-encoding` | Set the key encoding format (PKCS1 or PKCS8). | `PKCS1` | `PKCS8` | +| `csi.cert-manager.io/certificate-file` | File name to store the certificate file at. | `tls.crt` | `foo.crt` | +| `csi.cert-manager.io/ca-file` | File name to store the ca certificate file at. | `ca.crt` | `foo.ca` | +| `csi.cert-manager.io/privatekey-file` | File name to store the key file at. | `tls.key` | `foo.key` | +| `csi.cert-manager.io/fs-group` | Set the FS Group of written files. Should be paired with and match the value of the consuming container `runAsGroup`. | | `2000` | +| `csi.cert-manager.io/renew-before` | The time to renew the certificate before expiry. Defaults to a third of the requested duration. | `$CERT_DURATION/3` | `72h` | +| `csi.cert-manager.io/reuse-private-key` | Re-use the same private when when renewing certificates. | `false` | `true` | +| `csi.cert-manager.io/pkcs12-enable` | Enable writing the signed certificate chain and private key as a PKCS12 file. | | `true` | +| `csi.cert-manager.io/pkcs12-filename` | File location to write the PKCS12 file. Requires `csi.cert-manager.io/keystore-pkcs12-enable` be set to `true`. | `keystore.p12` | `tls.p12` | +| `csi.cert-manager.io/pkcs12-password` | Password used to encode the PKCS12 file. Required when PKCS12 is enabled (`csi.cert-manager.io/keystore-pkcs12-enable: true`). | | `my-password` | + +### Variables + +The following attributes support variables that are evaluated when a request is +made for the mounting Pod. These variables are useful for constructing requests +with SANs that contain values from the mounting Pod. + +``` +`csi.cert-manager.io/common-name` +`csi.cert-manager.io/dns-names` +`csi.cert-manager.io/uri-sans` +``` + +Variables follow the [go `os.Expand`](https://pkg.go.dev/os#Expand) structure, +which is generally what you would expect on a UNIX shell. The CSI driver has +access to the following variables: + +``` +${POD_NAME} +${POD_NAMESPACE} +${POD_UID} +${SERVICE_ACCOUNT_NAME} +``` + +#### Example Usage + +```yaml +volumeAttributes: + csi.cert-manager.io/issuer-name: ca-issuer + csi.cert-manager.io/dns-names: "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local" + csi.cert-manager.io/uri-sans: "spiffe://cluster.local/ns/${POD_NAMESPACE}/pod/${POD_NAME}/${POD_UID}" + csi.cert-manager.io/common-name: "${SERVICE_ACCOUNT_NAME}.${POD_NAMESPACE}" +``` + +## Requesting Certificates using the mounting Pod's ServiceAccount + +If the flag `--use-token-request` is enabled on the csi-driver DaemonSet, the +[CertificateRequest](../concepts/certificaterequest.md) resource will be created +by the mounting Pod's ServiceAccount. This can be pared with +[approver-policy](./approver-policy/README.md) to enable advanced policy on a per +ServiceAccount basis. + +Ensure to give permissions to Pod ServiceAccounts to create CertificateRequests +with this flag enabled, i.e: + +```yaml +# WARNING: This RBAC will enable any identiy in the cluster to create +# CertificateRequests. This may or may not be problimatic based on your security +# model. It is likely worth scoping the set of identities in the +# `ClusterRoleBinding` `subjects` stanza. +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: cert-manager-csi-driver-all-cr-create +rules: +- apiGroups: ["cert-manager.io"] + resources: ["certificaterequests"] + verbs: [ "create" ] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: cert-manager-csi-driver-all-cr-create +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-csi-driver-all-cr-create +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +``` diff --git a/content/v1.13-docs/projects/istio-csr.md b/content/v1.13-docs/projects/istio-csr.md new file mode 100644 index 0000000000..57748517bc --- /dev/null +++ b/content/v1.13-docs/projects/istio-csr.md @@ -0,0 +1,92 @@ +--- +title: istio-csr +description: '' +--- + +istio-csr is an agent that allows for [Istio](https://istio.io) workload and +control plane components to be secured using +[cert-manager](https://cert-manager.io). + +Certificates facilitating mTLS — both inter +and intra-cluster — will be signed, delivered and renewed using [cert-manager +issuers](https://cert-manager.io/docs/concepts/issuer). + +## Getting Started Guide For istio-csr + +We have [a guide](../tutorials/istio-csr/istio-csr.md) for setting up istio-csr in a fresh +[kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) cluster. + +Following the guide is the best way to see istio-csr in action. + +If you've already seen istio-csr in action or if you're experienced with running +Istio and just want quick installation instructions, read on for more details. + +## Lower-Level Details (For Experienced Istio Users) + +⚠️ The [getting started](../tutorials/istio-csr/istio-csr.md) guide is a better place if you just want to try istio-csr out! + +Running istio-csr requires a few steps and preconditions in order: + +1. A cluster _without_ Istio already installed +2. cert-manager [installed](https://cert-manager.io/docs/installation/) in the cluster +3. An `Issuer` or `ClusterIssuer` which will be used to issue Istio certificates +4. istio-csr installed (likely via helm) +5. Istio [installed](https://istio.io/latest/docs/setup/install/istioctl/) with + some custom config required, e.g. using the example config from the [repository](https://github.com/cert-manager/istio-csr/tree/main/hack). + +### Why Custom Istio Install Manifests? + +If you take a look at the contents of [the example Istio install +manifests](https://github.com/cert-manager/istio-csr/tree/main/hack) +there are a few custom configuration options which are important. + +Required changes include setting `ENABLE_CA_SERVER` to `false` and setting the `caAddress` from which Istio will +request certificates; replacing the CA server is the whole point of istio-csr! + +Mounting and statically specifying the root CA is also an important recommended step. Without a manually specified +root CA istio-csr defaults to trying to discover root CAs automatically, which could theoretically lead to a +[signer hijacking attack](https://github.com/cert-manager/istio-csr/issues/103#issuecomment-923882792) if for example +a signer's token was stolen (such as the cert-manager controller's token). + +### Issuer or ClusterIssuer? + +Unless you know you need a `ClusterIssuer` we'd recommend starting with an `Issuer`, since it should be easier to reason about +the access controls for an Issuer; they're namespaced and so naturally a little more limited in scope. + +That said, if you view your entire Kubernetes cluster as being a trust domain itself, then a ClusterIssuer is the more natural +fit. The best choice will depend on your specific situation. + +Our [getting started guide](../tutorials/istio-csr/istio-csr.md) uses an `Issuer`. + +### Which Issuer Type? + +Whether you choose to use an `Issuer` or a `ClusterIssuer`, you'll also need to choose the type of issuer you want such as: + +- [CA](https://cert-manager.io/docs/configuration/ca/) +- [Vault](https://cert-manager.io/docs/configuration/vault/) +- or an [external issuer](https://cert-manager.io/docs/configuration/external/) + +The key requirement is that arbitrary values can be placed into the `subjectAltName` (SAN) X.509 extension, since +Istio places SPIFFE IDs there. + +That means that the ACME issuer **will not work** — publicly trusted certificates such as those issued by Let's Encrypt +don't allow arbitrary entries in the SAN, for very good reasons. + +If you're already using [HashiCorp Vault](https://www.vaultproject.io/) then the Vault issuer is an obvious choice. If +you want to control your own PKI entirely, we'd recommend the CA issuer. The choice is ultimately yours. + +### Installing istio-csr After Istio + +This is unsupported because it's exceptionally difficult to do safely. It's likely that installing istio-csr _after_ Istio isn't +possible to do without downtime, since installing istio-csr second would require a time period where all Istio sidecars trust +both the old Istio-managed CA and the new cert-manager controlled CA. + +## How Does istio-csr Work? + +istio-csr implements the gRPC Istio certificate service which authenticates, +authorizes, and signs incoming certificate signing requests from Istio +workloads, routing all certificate handling through cert-manager installed in +the cluster. + +This seamlessly matches the behavior of istiod in a typical installation, while +allowing certificate management through cert-manager. diff --git a/content/v1.13-docs/projects/trust-manager/README.md b/content/v1.13-docs/projects/trust-manager/README.md new file mode 100644 index 0000000000..7507585c4b --- /dev/null +++ b/content/v1.13-docs/projects/trust-manager/README.md @@ -0,0 +1,417 @@ +--- +title: trust-manager +description: 'Distributing Trust Bundles in Kubernetes' +--- + +trust-manager is the easiest way to manage trust bundles in Kubernetes and OpenShift clusters. + +It orchestrates bundles of trusted X.509 certificates which are primarily used for validating +certificates during a TLS handshake but can be used in other situations, too. + +## Overview + +trust-manager is a small Kubernetes operator which aims to help reduce the overhead of managing +TLS trust bundles in your clusters. + +It adds the `Bundle` custom Kubernetes resource (CRD) which can read input from various sources +and combine the resultant certificates into a bundle ready to be used by your applications. + +trust-manager ensures that it's both quick and easy to keep your trusted certificates up-to-date +and enables cluster administrators to easily automate providing a secure bundle without having +to worry about rebuilding containers to update trust stores. + +It's designed to complement cert-manager and works well when consuming CA certificates from a +cert-manager `Issuer` or `ClusterIssuer` but can be used entirely independently from cert-manager +if needed. + +## Usage + +trust-manager is intentionally simple, adding just one new Kubernetes `CustomResourceDefintion`: `Bundle`. + +A `Bundle` represents a set of X.509 certificates that should be distributed across a cluster. + +All `Bundle`s are cluster scoped. + +`Bundle`s comprise a list of `sources` from which trust-manager will assemble the final bundle, along with +a `target` describing how and where the resulting bundle will be written. + +An example `Bundle` might look like this: + +```yaml +apiVersion: trust.cert-manager.io/v1alpha1 +kind: Bundle +metadata: + name: my-org.com # The bundle name will also be used for the target +spec: + sources: + # Include a bundle of publicly trusted certificates which can be + # used to validate most TLS certificates on the internet, such as + # those issued by Let's Encrypt, Google, Amazon and others. + - useDefaultCAs: true + + # A Secret in the "trust" namespace; see "Trust Namespace" below for further details + - secret: + name: "my-db-tls" + key: "ca.crt" + + # A ConfigMap in the "trust" namespace; see "Trust Namespace" below for further details + - configMap: + name: "my-org.net" + key: "root-certs.pem" + + # A manually specified string + - inLine: | + -----BEGIN CERTIFICATE----- + MIIC5zCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl + .... + 0V3NCaQrXoh+3xrXgX/vMdijYLUSo/YPEWmo + -----END CERTIFICATE----- + target: + # Sync the bundle to a ConfigMap called `my-org.com` in every namespace which + # has the label "linkerd.io/inject=enabled" + # All ConfigMaps will include a PEM-formatted bundle, here named "root-certs.pem" + # and in this case we also request a binary JKS formatted bundle, here named "bundle.jks" + configMap: + key: "root-certs.pem" + additionalFormats: + jks: + key: "bundle.jks" + namespaceSelector: + matchLabels: + linkerd.io/inject: "enabled" +``` + +`Bundle` resources currently support several source types: + +- `configMap` - a `ConfigMap` resource in the trust-manager namespace +- `secret` - a `Secret` resource in the trust-manager namespace +- `inLine` - a manually specified string containing at least one certificate +- `useDefaultCAs` - usually, a bundle of publicly trusted certificates + +These sources, along with the single currently supported target type (`configMap`) +are documented in the trust-manager [API reference documentation](./api-reference.md). + +#### Targets + +All `Bundle` targets are written to `ConfigMap`s whose name matches that of the `Bundle`, and every +target has a PEM-formatted bundle included. + +Users can also optionally - as of trust-manager v0.5.0 - choose to write a JKS formatted binary +bundle to the target. We understand that most Java applications tend to require a password on JKS +files (even though trust bundles don't contain secrets), so all trust-manager JKS bundles use the +default password `changeit`. + +#### Namespace Selector + +A target's `namespaceSelector` is used to restrict which Namespaces your `Bundle`'s target +should be synced to. + +`namespaceSelector` supports the field `matchLabels`. + +Please see [Kubernetes documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) +for more information about how label selectors can be configured. + +If `namespaceSelector` is empty, a `Bundle`'s target will be synced to all Namespaces. + +> ⚠️ A future update to trust-manager **will** change this behavior so that an empty namespace selector will sync only +to the trust-manager namespace by default. + +## Installation + +### Helm + +Helm is the easiest way to install trust-manager and comes with a publicly trusted certificate bundle package +(for the`useDefaultCAs` source) derived from Debian containers. + +When installed via Helm, trust-manager has a dependency on cert-manager for provisioning an application certificate, +and as such trust-manager is also installed into the cert-manager namespace. + +```bash +helm repo add jetstack https://charts.jetstack.io --force-update +helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set installCRDs=true --wait --create-namespace +helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --wait +``` + +### approver-policy Integration + +If you're running [approver-policy](../approver-policy/README.md) then cert-manager's default approver will be disabled which will mean that +trust-manager's webhook certificate will - by default - block when you install the Helm chart until it's manually approved. + +As of trust-manager v0.6.0 you can choose to automatically add an approver-policy `CertificateRequestPolicy` which +will approve the trust-manager webhook certificate: + +```bash +helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --set app.webhook.tls.approverPolicy.enabled=true --set app.webhook.tls.approverPolicy.certManagerNamespace=cert-manager --wait +``` + +Note that if you've installed cert-manager to a different namespace, you'll need to pass that namespace in `app.webhook.tls.approverPolicy.certManagerNamespace`! + +### Manual Installation + +We strongly recommend that you install trust-manager using Helm and we don't currently support manually installed +versions of trust-manager. This is so that we can focus on continuing to improve trust-manager with the resources +we currently have available. + +### Trust Namespace + +One of the more important configuration options you might need to consider at install time is which "trust namespace" to use, +which can be set via the Helm value `app.trust.namespace`. + +The trust namespace is the only one in which `Secret` and `ConfigMap` sources can be read. This restriction is in place +for security reasons - we don't want to give trust-manager the permission to read all `Secret`s or `ConfigMap`s in all namespaces. + +The trust namespace defaults to `cert-manager`, but there's no need for it to be set to the namespace that cert-manager +is installed in - trust-manager has no runtime dependency on cert-manager at all! - so we'd recommend setting the trust +namespace to whichever is most appropriate for your environment. + +An ideal deployment would be a fresh namespace dedicated entirely to trust-manager, to minimize the number of actors in your +cluster that can modify your trust sources. + +## Quick Start Example + +Let's get started with an example of creating our own `Bundle`! + +First we'll create a demo cluster: + +```bash +git clone https://github.com/cert-manager/trust-manager trust-manager +cd trust-manager +make demo +``` + +Once we have a running cluster, we can create a `Bundle` using the default CAs which were configured +when trust-manager started up. Since we've installed trust-manager using Helm, our default CA package +contains publicly trusted certificates derived from a Debian container. + +```bash +kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - < 🤔 Wondering why we used `tls.crt` and not `ca.crt`? More details [below](./README.md#preparing-for-production). + +Finally, we'll update our `Bundle` to include our new private CA: + +```bash +kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - < ⚠️ This upgrade process assumes that it's the only thing running. If another user or process changes Helm values +> while you're doing this process, you might overwrite their work. + +First, we'll dump our current Helm values, so we don't lose them: + +```bash +helm get values -n cert-manager trust-manager -oyaml > values.yaml +``` + +Next, if `defaultPackageImage.tag` is already set in `values.yaml`, update it. Otherwise, add it. +You can find the available tags [on `quay.io`](https://quay.io/repository/jetstack/cert-manager-package-debian?tab=tags&tag=latest). + +```yaml +# values.yaml +... +defaultPackageImage: + tag: XYZ +``` + +These versions of the default package image tags are derived directly from the version of the `ca-certificates` package in Debian. + +Finally, apply back the changes, being sure to manually specify the version of trust-manager which is installed, to avoid +also updating the trust-manager controller at the same as the default CA package: + +```bash +# Get the currently installed version. You could do this manually if you find that easier. +TRUST_MANAGER_VER=$(helm list --filter "^trust-manager$" -n cert-manager -ojson | jq -r ".[0].app_version") + +# Check the version makes sense +echo $TRUST_MANAGER_VER + +# Run the upgrade +helm upgrade -f values.yaml -n cert-manager trust-manager jetstack/trust-manager --version $TRUST_MANAGER_VER +``` + +If an incorrect tag is used, your deployment will fail and you'll likely need to use `helm rollback` to get back +to a working state. + +## Preparing for Production + +TLS can be complicated and there are many ways to misuse TLS certificates. + +Here are some potential gotchas here to be aware of before running trust-manager in production. + +If you're planning on running trust-manager in production and you're using more than just the default CA package, +we **strongly** advise you to read and understand this section. It could save you from causing an outage later. + +> ℹ️ These gotchas aren't specific to trust-manager and you could run into any of them with any method of managing TLS trust! + +### Bundling Intermediates + +If you've ever used a Let's Encrypt client such as [Certbot](https://certbot.eff.org/) you'll probably have +seen that it generates several certificate files, such as `cert.pem`, `chain.pem`, and `fullchain.pem`. + +These various files are provided to support various different applications, which might require the certificate +and the chain to be given separately. For most users and applications `fullchain.pem` is the only correct choice. + +Unfortunately the existence of these files has the unfortunate side effect of people sometimes assuming that `cert.pem` +is the correct choice even when `fullchain.pem` would be correct. This means that the rest of the chain will not +be sent when the certificate is used. + +Often, a quick fix that _seems_ to work for this is that clients add the chain to their trust store, which will seem +to fix certificate errors in the short term. It's easy for this kind of "fix" to end up being embedded somewhere as a +solution which others can follow. + +This "fix" is dangerous; it means that the intermediate cannot be safely rotated without all trust stores +which contain it being updated first. + +Intermediates in this case become _de facto_ root certificates, which completely defeats the point of having +intermediate certificates in the first place. + +Avoid using intermediates in any trust store wherever possible unless you're absolutely certain they should be included. +An example of where it might be OK would be cross signing, which is not likely to be required in the general case. + +It would be better to copy just the root certificate to a new `ConfigMap` and use that as a source rather than trusting +an intermediate. + +### cert-manager Integration: `ca.crt` vs `tls.crt` + +If you're pointing trust-manager at a `Secret` containing a cert-manager-issued certificate, you'll see two relevant +fields: `ca.crt` and `tls.crt`. (We're ignoring `tls.key` - trust-manager definitely doesn't need to access that) + +That leads to an obvious question: between `ca.crt` and `tls.crt`, which should I use for trust-manager? + +Unfortunately, it's impossible to say in the general case which field is correct to use, but we can provide guidelines. + +`tls.crt` will generally contain multiple certificates which may not all be issuers and some of which are likely to be +intermediate certificates. If that's the case, you shouldn't use `tls.crt` as a source. (See "Bundling Intermediates" above for details.) + +`ca.crt` might then seem like the more generally correct choice but it's important to bear in mind that it can only ever +be populated on a best-effort basis. The contents of `ca.crt` depend on the `Issuer` being configured correctly, and some +issuer types may not ever be able to provide a useful or correct entry for this field. + +As a rule, you should prefer to create `Bundles` exclusively using root certificates (again, see above), and so you should +only use whichever field has a single root certificate in it. Consider reading below about why you might not want to +actually rely directly on cert-manager-issued certificates. + +### cert-manager Integration: Intentionally Copying CA Certificates + +It's very strange in the Kubernetes world to suggest intentionally adding a step which seems to make automating infrastructure +harder, but in the case of TLS trust stores it can be a wise choice. + +Say you have a cert-manager `Issuer` which has the root certificate you want to trust in `ca.crt`. It's tempting to +use the `Secret` directly and point at `ca.crt`, but a best practice would be to copy that root into a separate `ConfigMap` +(or `Secret`). + +The reason is - as with many TLS gotchas - certificate rotation. If you rotate your issuer such that it's issued from a new root +certificate, trust-manager will see the `Secret` be updated and automatically update your trust bundle to include the new root - +immediately distrusting the old root. + +That means that if any services were still using a certificate issued by the old root, they'll be distrusted and will break. + +Rotation requires that both root certificates are trusted simultaneously for a period, or else that all issued certificates +are rotated either before or at the same time as the old root. + +## Known Issues + +### `kubectl describe` + +The `useDefaultCAs` option hits a corner case inside `kubectl describe` and is rendered as `Use Default C As: true`. This is +purely cosmetic. diff --git a/content/v1.13-docs/projects/trust-manager/api-reference.md b/content/v1.13-docs/projects/trust-manager/api-reference.md new file mode 100644 index 0000000000..da09c7c039 --- /dev/null +++ b/content/v1.13-docs/projects/trust-manager/api-reference.md @@ -0,0 +1,592 @@ +--- +title: trust-manager API Reference +description: "trust-manager API documentation for custom resources" +--- + +Packages: + +- [`trust.cert-manager.io/v1alpha1`](#trustcert-manageriov1alpha1) + +# `trust.cert-manager.io/v1alpha1` + +Resource Types: + + +- [Bundle](#bundle) + + + + +## `Bundle` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        apiVersionstringtrust.cert-manager.io/v1alpha1true
        kindstringBundletrue
        metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
        specobject + Desired state of the Bundle resource.
        +
        true
        statusobject + Status of the Bundle. This is set and managed automatically.
        +
        false
        + + +### `Bundle.spec` + + +Desired state of the Bundle resource. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        sources[]object + Sources is a set of references to data whose data will sync to the target.
        +
        true
        targetobject + Target is the target location in all namespaces to sync source data to.
        +
        true
        + + +### `Bundle.spec.sources[index]` + + +BundleSource is the set of sources whose data will be appended and synced to the BundleTarget in all Namespaces. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        configMapobject + ConfigMap is a reference to a ConfigMap's `data` key, in the trust Namespace.
        +
        false
        inLinestring + InLine is a simple string to append as the source data.
        +
        false
        secretobject + Secret is a reference to a Secrets's `data` key, in the trust Namespace.
        +
        false
        useDefaultCAsboolean + UseDefaultCAs, when true, requests the default CA bundle to be used as a source. Default CAs are available if trust-manager was installed via Helm or was otherwise set up to include a package-injecting init container by using the "--default-package-location" flag when starting the trust-manager controller. If default CAs were not configured at start-up, any request to use the default CAs will fail. The version of the default CA package which is used for a Bundle is stored in the defaultCAPackageVersion field of the Bundle's status field.
        +
        false
        + + +### `Bundle.spec.sources[index].configMap` + + +ConfigMap is a reference to a ConfigMap's `data` key, in the trust Namespace. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        keystring + Key is the key of the entry in the object's `data` field to be used.
        +
        true
        namestring + Name is the name of the source object in the trust Namespace.
        +
        true
        + + +### `Bundle.spec.sources[index].secret` + + +Secret is a reference to a Secrets's `data` key, in the trust Namespace. + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        keystring + Key is the key of the entry in the object's `data` field to be used.
        +
        true
        namestring + Name is the name of the source object in the trust Namespace.
        +
        true
        + + +### `Bundle.spec.target` + + +Target is the target location in all namespaces to sync source data to. + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        additionalFormatsobject + AdditionalFormats specifies any additional formats to write to the target
        +
        false
        configMapobject + ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
        +
        false
        namespaceSelectorobject + NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
        +
        false
        + + +### `Bundle.spec.target.additionalFormats` + + +AdditionalFormats specifies any additional formats to write to the target + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        jksobject + JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit".
        +
        false
        + + +### `Bundle.spec.target.additionalFormats.jks` + + +JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit". + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        keystring + Key is the key of the entry in the object's `data` field to be used.
        +
        true
        + + +### `Bundle.spec.target.configMap` + + +ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to. + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        keystring + Key is the key of the entry in the object's `data` field to be used.
        +
        true
        + + +### `Bundle.spec.target.namespaceSelector` + + +NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector. + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        matchLabelsmap[string]string + MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
        +
        false
        + + +### `Bundle.status` + + +Status of the Bundle. This is set and managed automatically. + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        conditions[]object + List of status conditions to indicate the status of the Bundle. Known condition types are `Bundle`.
        +
        false
        defaultCAVersionstring + DefaultCAPackageVersion, if set and non-empty, indicates the version information which was retrieved when the set of default CAs was requested in the bundle source. This should only be set if useDefaultCAs was set to "true" on a source, and will be the same for the same version of a bundle with identical certificates.
        +
        false
        targetobject + Target is the current Target that the Bundle is attempting or has completed syncing the source data to.
        +
        false
        + + +### `Bundle.status.conditions[index]` + + +BundleCondition contains condition information for a Bundle. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        statusstring + Status of the condition, one of ('True', 'False', 'Unknown').
        +
        true
        typestring + Type of the condition, known values are (`Synced`).
        +
        true
        lastTransitionTimestring + LastTransitionTime is the timestamp corresponding to the last status change of this condition.
        +
        + Format: date-time
        +
        false
        messagestring + Message is a human readable description of the details of the last transition, complementing reason.
        +
        false
        observedGenerationinteger + If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Bundle.
        +
        + Format: int64
        +
        false
        reasonstring + Reason is a brief machine readable explanation for the condition's last transition.
        +
        false
        + + +### `Bundle.status.target` + + +Target is the current Target that the Bundle is attempting or has completed syncing the source data to. + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        additionalFormatsobject + AdditionalFormats specifies any additional formats to write to the target
        +
        false
        configMapobject + ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
        +
        false
        namespaceSelectorobject + NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
        +
        false
        + + +### `Bundle.status.target.additionalFormats` + + +AdditionalFormats specifies any additional formats to write to the target + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        jksobject + JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit".
        +
        false
        + + +### `Bundle.status.target.additionalFormats.jks` + + +JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit". + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        keystring + Key is the key of the entry in the object's `data` field to be used.
        +
        true
        + + +### `Bundle.status.target.configMap` + + +ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to. + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        keystring + Key is the key of the entry in the object's `data` field to be used.
        +
        true
        + + +### `Bundle.status.target.namespaceSelector` + + +NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector. + + + + + + + + + + + + + + + + +
        NameTypeDescriptionRequired
        matchLabelsmap[string]string + MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
        +
        false
        diff --git a/content/v1.13-docs/reference/README.md b/content/v1.13-docs/reference/README.md new file mode 100644 index 0000000000..473828e355 --- /dev/null +++ b/content/v1.13-docs/reference/README.md @@ -0,0 +1,15 @@ +--- +title: Reference +description: Reference material including TLS terminology, API documentation, and information about the command line flags of the cert-manager components. +--- + +This section contains reference material including TLS terminology, API documentation, and information about the command line flags of the cert-manager components. + +* [TLS Terminology](./tls-terminology.md): + Learn about the TLS terminology used in the cert-manager documentation such as `publicly trusted`, `self-signed`, `root`, `intermediate` and `leaf` _certificate_. + +* [Components / Docker Images](../cli/README.md): + Learn about the command line flags of the cert-manager Docker images: `controller`, `webhook`, `cainjector`, `acmesolver`, which run in containers in your cluster. + +* [API Reference](./api-docs.md): + Learn about the cert-manager API which includes Custom Resources such as Certificate, CertificateRequest, Issuer and ClusterIssuer. diff --git a/content/v1.13-docs/reference/api-docs.md b/content/v1.13-docs/reference/api-docs.md new file mode 100644 index 0000000000..b3d73a0e55 --- /dev/null +++ b/content/v1.13-docs/reference/api-docs.md @@ -0,0 +1,6380 @@ +--- +title: API Reference +description: >- + cert-manager API documentation, including Custom Resources such as + Certificate, CertificateRequest, Issuer and ClusterIssuer +--- +

        cert-manager API documentation, including various Custom Resource Definitions

        +

        Packages:

        + +

        acme.cert-manager.io/v1

        +
        +

        Package v1 is the v1 version of the API.

        +
        +

        Resource Types:

        + +

        Challenge

        +
        +

        Challenge is a type to represent a Challenge request with an ACME server

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + apiVersion +
        + string +
        + acme.cert-manager.io/v1 +
        + kind +
        + string +
        + Challenge +
        + metadata +
        + + Kubernetes meta/v1.ObjectMeta + +
        + Refer to the Kubernetes API documentation for the fields of the + metadata field. +
        + spec +
        + + ChallengeSpec + +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + url +
        + string +
        +

        The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge.

        +
        + authorizationURL +
        + string +
        +

        The URL to the ACME Authorization resource that this challenge is a part of.

        +
        + dnsName +
        + string +
        +

        dnsName is the identifier that this challenge is for, e.g. example.com. If the requested DNSName is a ‘wildcard’, this field MUST be set to the non-wildcard domain, e.g. for *.example.com, it must be example.com.

        +
        + wildcard +
        + bool +
        + (Optional) +

        wildcard will be true if this challenge is for a wildcard identifier, for example ‘*.example.com’.

        +
        + type +
        + + ACMEChallengeType + +
        +

        The type of ACME challenge this resource represents. One of “HTTP-01” or “DNS-01”.

        +
        + token +
        + string +
        +

        The ACME challenge token for this challenge. This is the raw value returned from the ACME server.

        +
        + key +
        + string +
        +

        + The ACME challenge key for this challenge For HTTP01 challenges, this is the value that must be responded with to complete the HTTP01 challenge in the format: + <private key JWK thumbprint>.<key from acme server for challenge>. For DNS01 challenges, this is the base64 encoded SHA256 sum of the + <private key JWK thumbprint>.<key from acme server for challenge> + text that must be set as the TXT record content. +

        +
        + solver +
        + + ACMEChallengeSolver + +
        +

        Contains the domain solving configuration that should be used to solve this challenge resource.

        +
        + issuerRef +
        + + ObjectReference + +
        +

        References a properly configured ACME-type Issuer which should be used to create this Challenge. If the Issuer does not exist, processing will be retried. If the Issuer is not an ‘ACME’ Issuer, an error will be returned and the Challenge will be marked as failed.

        +
        +
        + status +
        + + ChallengeStatus + +
        + (Optional) +
        +

        Order

        +
        +

        Order is a type to represent an Order with an ACME server

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + apiVersion +
        + string +
        + acme.cert-manager.io/v1 +
        + kind +
        + string +
        + Order +
        + metadata +
        + + Kubernetes meta/v1.ObjectMeta + +
        + Refer to the Kubernetes API documentation for the fields of the + metadata field. +
        + spec +
        + + OrderSpec + +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        + request +
        + []byte +
        +

        Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order.

        +
        + issuerRef +
        + + ObjectReference + +
        +

        IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an ‘ACME’ Issuer, an error will be returned and the Order will be marked as failed.

        +
        + commonName +
        + string +
        + (Optional) +

        CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in dnsNames or ipAddresses. This field must match the corresponding field on the DER encoded CSR.

        +
        + dnsNames +
        + []string +
        + (Optional) +

        DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.

        +
        + ipAddresses +
        + []string +
        + (Optional) +

        IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.

        +
        + duration +
        + + Kubernetes meta/v1.Duration + +
        + (Optional) +

        Duration is the duration for the not after date for the requested certificate. this is set on order creation as pe the ACME spec.

        +
        +
        + status +
        + + OrderStatus + +
        + (Optional) +
        +

        ACMEAuthorization

        +

        (Appears on: OrderStatus)

        +
        +

        ACMEAuthorization contains data returned from the ACME server on an authorization that must be completed in order validate a DNS name on an ACME Order resource.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + url +
        + string +
        +

        URL is the URL of the Authorization that must be completed

        +
        + identifier +
        + string +
        + (Optional) +

        Identifier is the DNS name to be validated as part of this authorization

        +
        + wildcard +
        + bool +
        + (Optional) +

        Wildcard will be true if this authorization is for a wildcard DNS name. If this is true, the identifier will be the non-wildcard version of the DNS name. For example, if ‘*.example.com’ is the DNS name being validated, this field will be ‘true’ and the ‘identifier’ field will be ‘example.com’.

        +
        + initialState +
        + + State + +
        + (Optional) +

        InitialState is the initial state of the ACME authorization when first fetched from the ACME server. If an Authorization is already ‘valid’, the Order controller will not create a Challenge resource for the authorization. This will occur when working with an ACME server that enables ‘authz reuse’ (such as Let’s Encrypt’s production endpoint). If not set and ‘identifier’ is set, the state is assumed to be pending and a Challenge will be created.

        +
        + challenges +
        + + []ACMEChallenge + +
        + (Optional) +

        Challenges specifies the challenge types offered by the ACME server. One of these challenge types will be selected when validating the DNS name and an appropriate Challenge resource will be created to perform the ACME challenge process.

        +
        +

        ACMEChallenge

        +

        (Appears on: ACMEAuthorization)

        +
        +

        Challenge specifies a challenge offered by the ACME server for an Order. An appropriate Challenge resource can be created to perform the ACME challenge process.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + url +
        + string +
        +

        URL is the URL of this challenge. It can be used to retrieve additional metadata about the Challenge from the ACME server.

        +
        + token +
        + string +
        +

        Token is the token that must be presented for this challenge. This is used to compute the ‘key’ that must also be presented.

        +
        + type +
        + string +
        +

        Type is the type of challenge being offered, e.g. ‘http-01’, ‘dns-01’, ‘tls-sni-01’, etc. This is the raw value retrieved from the ACME server. Only ‘http-01’ and ‘dns-01’ are supported by cert-manager, other values will be ignored.

        +
        +

        ACMEChallengeSolver

        +

        (Appears on: ACMEIssuer, ChallengeSpec)

        +
        +

        An ACMEChallengeSolver describes how to solve ACME challenges for the issuer it is part of. A selector may be provided to use different solving strategies for different DNS names. Only one of HTTP01 or DNS01 must be provided.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + selector +
        + + CertificateDNSNameSelector + +
        + (Optional) +

        Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the ‘default’ solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead.

        +
        + http01 +
        + + ACMEChallengeSolverHTTP01 + +
        + (Optional) +

        Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. *.example.com) using the HTTP01 challenge mechanism.

        +
        + dns01 +
        + + ACMEChallengeSolverDNS01 + +
        + (Optional) +

        Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow.

        +
        +

        ACMEChallengeSolverDNS01

        +

        (Appears on: ACMEChallengeSolver)

        +
        +

        Used to configure a DNS01 challenge provider to be used when solving DNS01 challenges. Only one DNS provider may be configured per solver.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + cnameStrategy +
        + + CNAMEStrategy + +
        + (Optional) +

        CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones.

        +
        + akamai +
        + + ACMEIssuerDNS01ProviderAkamai + +
        + (Optional) +

        Use the Akamai DNS zone management API to manage DNS01 challenge records.

        +
        + cloudDNS +
        + + ACMEIssuerDNS01ProviderCloudDNS + +
        + (Optional) +

        Use the Google Cloud DNS API to manage DNS01 challenge records.

        +
        + cloudflare +
        + + ACMEIssuerDNS01ProviderCloudflare + +
        + (Optional) +

        Use the Cloudflare API to manage DNS01 challenge records.

        +
        + route53 +
        + + ACMEIssuerDNS01ProviderRoute53 + +
        + (Optional) +

        Use the AWS Route53 API to manage DNS01 challenge records.

        +
        + azureDNS +
        + + ACMEIssuerDNS01ProviderAzureDNS + +
        + (Optional) +

        Use the Microsoft Azure DNS API to manage DNS01 challenge records.

        +
        + digitalocean +
        + + ACMEIssuerDNS01ProviderDigitalOcean + +
        + (Optional) +

        Use the DigitalOcean DNS API to manage DNS01 challenge records.

        +
        + acmeDNS +
        + + ACMEIssuerDNS01ProviderAcmeDNS + +
        + (Optional) +

        Use the ‘ACME DNS’ (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records.

        +
        + rfc2136 +
        + + ACMEIssuerDNS01ProviderRFC2136 + +
        + (Optional) +

        Use RFC2136 (“Dynamic Updates in the Domain Name System”) (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records.

        +
        + webhook +
        + + ACMEIssuerDNS01ProviderWebhook + +
        + (Optional) +

        Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records.

        +
        +

        ACMEChallengeSolverHTTP01

        +

        (Appears on: ACMEChallengeSolver)

        +
        +

        ACMEChallengeSolverHTTP01 contains configuration detailing how to solve HTTP01 challenges within a Kubernetes cluster. Typically this is accomplished through creating ‘routes’ of some description that configure ingress controllers to direct traffic to ‘solver pods’, which are responsible for responding to the ACME server’s HTTP requests. Only one of Ingress / Gateway can be specified.

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + ingress +
        + + ACMEChallengeSolverHTTP01Ingress + +
        + (Optional) +

        The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for ‘/.well-known/acme-challenge/XYZ’ to ‘challenge solver’ pods that are provisioned by cert-manager for each Challenge to be completed.

        +
        + gatewayHTTPRoute +
        + + ACMEChallengeSolverHTTP01GatewayHTTPRoute + +
        + (Optional) +

        The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future.

        +
        +

        ACMEChallengeSolverHTTP01GatewayHTTPRoute

        +

        (Appears on: ACMEChallengeSolverHTTP01)

        +
        +

        The ACMEChallengeSolverHTTP01GatewayHTTPRoute solver will create HTTPRoute objects for a Gateway class routing to an ACME challenge solver pod.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + serviceType +
        + + Kubernetes core/v1.ServiceType + +
        + (Optional) +

        Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort.

        +
        + labels +
        + map[string]string +
        + (Optional) +

        Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges.

        +
        + parentRefs +
        + []sigs.k8s.io/gateway-api/apis/v1beta1.ParentReference +
        +

        + When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: + https://gateway-api.sigs.k8s.io/api-types/httproute/#attaching-to-gateways +

        +
        +

        ACMEChallengeSolverHTTP01Ingress

        +

        (Appears on: ACMEChallengeSolverHTTP01)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + serviceType +
        + + Kubernetes core/v1.ServiceType + +
        + (Optional) +

        Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort.

        +
        + ingressClassName +
        + string +
        + (Optional) +

        This field configures the field ingressClassName on the created Ingress resources used to solve ACME challenges that use this challenge solver. This is the recommended way of configuring the ingress class. Only one of class, name or ingressClassName may be specified.

        +
        + class +
        + string +
        + (Optional) +

        This field configures the annotation kubernetes.io/ingress.class when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of class, name or ingressClassName may be specified.

        +
        + name +
        + string +
        + (Optional) +

        The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. Only one of class, name or ingressClassName may be specified.

        +
        + podTemplate +
        + + ACMEChallengeSolverHTTP01IngressPodTemplate + +
        + (Optional) +

        Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges.

        +
        + ingressTemplate +
        + + ACMEChallengeSolverHTTP01IngressTemplate + +
        + (Optional) +

        Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges.

        +
        +

        ACMEChallengeSolverHTTP01IngressObjectMeta

        +

        (Appears on: ACMEChallengeSolverHTTP01IngressTemplate)

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + annotations +
        + map[string]string +
        + (Optional) +

        Annotations that should be added to the created ACME HTTP01 solver ingress.

        +
        + labels +
        + map[string]string +
        + (Optional) +

        Labels that should be added to the created ACME HTTP01 solver ingress.

        +
        +

        ACMEChallengeSolverHTTP01IngressPodObjectMeta

        +

        (Appears on: ACMEChallengeSolverHTTP01IngressPodTemplate)

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + annotations +
        + map[string]string +
        + (Optional) +

        Annotations that should be added to the create ACME HTTP01 solver pods.

        +
        + labels +
        + map[string]string +
        + (Optional) +

        Labels that should be added to the created ACME HTTP01 solver pods.

        +
        +

        ACMEChallengeSolverHTTP01IngressPodSpec

        +

        (Appears on: ACMEChallengeSolverHTTP01IngressPodTemplate)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + nodeSelector +
        + map[string]string +
        + (Optional) +

        NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node’s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

        +
        + affinity +
        + + Kubernetes core/v1.Affinity + +
        + (Optional) +

        If specified, the pod’s scheduling constraints

        +
        + tolerations +
        + + []Kubernetes core/v1.Toleration + +
        + (Optional) +

        If specified, the pod’s tolerations.

        +
        + priorityClassName +
        + string +
        + (Optional) +

        If specified, the pod’s priorityClassName.

        +
        + serviceAccountName +
        + string +
        + (Optional) +

        If specified, the pod’s service account

        +
        + imagePullSecrets +
        + + []Kubernetes core/v1.LocalObjectReference + +
        + (Optional) +

        If specified, the pod’s imagePullSecrets

        +
        +

        ACMEChallengeSolverHTTP01IngressPodTemplate

        +

        (Appears on: ACMEChallengeSolverHTTP01Ingress)

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + metadata +
        + + ACMEChallengeSolverHTTP01IngressPodObjectMeta + +
        + (Optional) +

        ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the ‘labels’ and ‘annotations’ fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values.

        +
        + spec +
        + + ACMEChallengeSolverHTTP01IngressPodSpec + +
        + (Optional) +

        PodSpec defines overrides for the HTTP01 challenge solver pod. Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. All other fields will be ignored.

        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        + nodeSelector +
        + map[string]string +
        + (Optional) +

        NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node’s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

        +
        + affinity +
        + + Kubernetes core/v1.Affinity + +
        + (Optional) +

        If specified, the pod’s scheduling constraints

        +
        + tolerations +
        + + []Kubernetes core/v1.Toleration + +
        + (Optional) +

        If specified, the pod’s tolerations.

        +
        + priorityClassName +
        + string +
        + (Optional) +

        If specified, the pod’s priorityClassName.

        +
        + serviceAccountName +
        + string +
        + (Optional) +

        If specified, the pod’s service account

        +
        + imagePullSecrets +
        + + []Kubernetes core/v1.LocalObjectReference + +
        + (Optional) +

        If specified, the pod’s imagePullSecrets

        +
        +
        +

        ACMEChallengeSolverHTTP01IngressTemplate

        +

        (Appears on: ACMEChallengeSolverHTTP01Ingress)

        +
        + + + + + + + + + + + + + +
        FieldDescription
        + metadata +
        + + ACMEChallengeSolverHTTP01IngressObjectMeta + +
        + (Optional) +

        ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the ‘labels’ and ‘annotations’ fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values.

        +
        +

        ACMEChallengeType (string alias)

        +

        (Appears on: ChallengeSpec)

        +
        +

        The type of ACME challenge. Only HTTP-01 and DNS-01 are supported.

        +
        + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "DNS-01"

        +
        +

        ACMEChallengeTypeDNS01 denotes a Challenge is of type dns-01 More info: https://letsencrypt.org/docs/challenge-types/#dns-01-challenge

        +
        +

        "HTTP-01"

        +
        +

        ACMEChallengeTypeHTTP01 denotes a Challenge is of type http-01 More info: https://letsencrypt.org/docs/challenge-types/#http-01-challenge

        +
        +

        ACMEExternalAccountBinding

        +

        (Appears on: ACMEIssuer)

        +
        +

        ACMEExternalAccountBinding is a reference to a CA external account of the ACME server.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + keyID +
        + string +
        +

        keyID is the ID of the CA key that the External Account is bound to.

        +
        + keySecretRef +
        + + SecretKeySelector + +
        +

        keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes Secret which holds the symmetric MAC key of the External Account Binding. The key is the index string that is paired with the key data in the Secret and should not be confused with the key data itself, or indeed with the External Account Binding keyID above. The secret key stored in the Secret must be un-padded, base64 URL encoded data.

        +
        + keyAlgorithm +
        + + HMACKeyAlgorithm + +
        + (Optional) +

        Deprecated: keyAlgorithm field exists for historical compatibility reasons and should not be used. The algorithm is now hardcoded to HS256 in golang/x/crypto/acme.

        +
        +

        ACMEIssuer

        +

        (Appears on: IssuerConfig)

        +
        +

        ACMEIssuer contains the specification for an ACME issuer. This uses the RFC8555 specification to obtain certificates by completing ‘challenges’ to prove ownership of domain identifiers. Earlier draft versions of the ACME specification are not supported.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + email +
        + string +
        + (Optional) +

        Email is the email address to be associated with the ACME account. This field is optional, but it is strongly recommended to be set. It will be used to contact you in case of issues with your account or certificates, including expiry notification emails. This field may be updated after the account is initially registered.

        +
        + server +
        + string +
        +

        Server is the URL used to access the ACME server’s ‘directory’ endpoint. For example, for Let’s Encrypt’s staging endpoint, you would use: “https://acme-staging-v02.api.letsencrypt.org/directory”. Only ACME v2 endpoints (i.e. RFC 8555) are supported.

        +
        + preferredChain +
        + string +
        + (Optional) +

        PreferredChain is the chain to use if the ACME server outputs multiple. PreferredChain is no guarantee that this one gets delivered by the ACME endpoint. For example, for Let’s Encrypt’s DST crosssign you would use: “DST Root CA X3” or “ISRG Root X1” for the newer Let’s Encrypt root CA. This value picks the first certificate bundle in the ACME alternative chains that has a certificate with this value as its issuer’s CN

        +
        + caBundle +
        + []byte +
        + (Optional) +

        Base64-encoded bundle of PEM CAs which can be used to validate the certificate chain presented by the ACME server. Mutually exclusive with SkipTLSVerify; prefer using CABundle to prevent various kinds of security vulnerabilities. If CABundle and SkipTLSVerify are unset, the system certificate bundle inside the container is used to validate the TLS connection.

        +
        + skipTLSVerify +
        + bool +
        + (Optional) +

        INSECURE: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have the TLS certificate chain validated. Mutually exclusive with CABundle; prefer using CABundle to prevent various kinds of security vulnerabilities. Only enable this option in development environments. If CABundle and SkipTLSVerify are unset, the system certificate bundle inside the container is used to validate the TLS connection. Defaults to false.

        +
        + externalAccountBinding +
        + + ACMEExternalAccountBinding + +
        + (Optional) +

        ExternalAccountBinding is a reference to a CA external account of the ACME server. If set, upon registration cert-manager will attempt to associate the given external account credentials with the registered ACME account.

        +
        + privateKeySecretRef +
        + + SecretKeySelector + +
        +

        PrivateKey is the name of a Kubernetes Secret resource that will be used to store the automatically generated ACME account private key. Optionally, a key may be specified to select a specific entry within the named Secret resource. If key is not specified, a default of tls.key will be used.

        +
        + solvers +
        + + []ACMEChallengeSolver + +
        + (Optional) +

        Solvers is a list of challenge solvers that will be used to solve ACME challenges for the matching domains. Solver configurations must be provided in order to obtain certificates from an ACME server. For more information, see: https://cert-manager.io/docs/configuration/acme/

        +
        + disableAccountKeyGeneration +
        + bool +
        + (Optional) +

        Enables or disables generating a new ACME account key. If true, the Issuer resource will not request a new account but will expect the account key to be supplied via an existing secret. If false, the cert-manager system will generate a new ACME account key for the Issuer. Defaults to false.

        +
        + enableDurationFeature +
        + bool +
        + (Optional) +

        Enables requesting a Not After date on certificates that matches the duration of the certificate. This is not supported by all ACME servers like Let’s Encrypt. If set to true when the ACME server does not support it it will create an error on the Order. Defaults to false.

        +
        +

        ACMEIssuerDNS01ProviderAcmeDNS

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        ACMEIssuerDNS01ProviderAcmeDNS is a structure containing the configuration for ACME-DNS servers

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + host +
        + string +
        + accountSecretRef +
        + + SecretKeySelector + +
        +

        ACMEIssuerDNS01ProviderAkamai

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        ACMEIssuerDNS01ProviderAkamai is a structure containing the DNS configuration for Akamai DNS—Zone Record Management API

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + serviceConsumerDomain +
        + string +
        + clientTokenSecretRef +
        + + SecretKeySelector + +
        + clientSecretSecretRef +
        + + SecretKeySelector + +
        + accessTokenSecretRef +
        + + SecretKeySelector + +
        +

        ACMEIssuerDNS01ProviderAzureDNS

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        ACMEIssuerDNS01ProviderAzureDNS is a structure containing the configuration for Azure DNS

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + clientID +
        + string +
        + (Optional) +

        if both this and ClientSecret are left unset MSI will be used

        +
        + clientSecretSecretRef +
        + + SecretKeySelector + +
        + (Optional) +

        if both this and ClientID are left unset MSI will be used

        +
        + subscriptionID +
        + string +
        +

        ID of the Azure subscription

        +
        + tenantID +
        + string +
        + (Optional) +

        when specifying ClientID and ClientSecret then this field is also needed

        +
        + resourceGroupName +
        + string +
        +

        resource group the DNS zone is located in

        +
        + hostedZoneName +
        + string +
        + (Optional) +

        name of the DNS zone that should be used

        +
        + environment +
        + + AzureDNSEnvironment + +
        + (Optional) +

        name of the Azure environment (default AzurePublicCloud)

        +
        + managedIdentity +
        + + AzureManagedIdentity + +
        + (Optional) +

        managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID

        +
        +

        ACMEIssuerDNS01ProviderCloudDNS

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS configuration for Google Cloud DNS

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + serviceAccountSecretRef +
        + + SecretKeySelector + +
        + (Optional) +
        + project +
        + string +
        + hostedZoneName +
        + string +
        + (Optional) +

        HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone.

        +
        +

        ACMEIssuerDNS01ProviderCloudflare

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS configuration for Cloudflare. One of apiKeySecretRef or apiTokenSecretRef must be provided.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + email +
        + string +
        + (Optional) +

        Email of the account, only required when using API key based authentication.

        +
        + apiKeySecretRef +
        + + SecretKeySelector + +
        + (Optional) +

        API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.

        +
        + apiTokenSecretRef +
        + + SecretKeySelector + +
        + (Optional) +

        API token used to authenticate with Cloudflare.

        +
        +

        ACMEIssuerDNS01ProviderDigitalOcean

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        ACMEIssuerDNS01ProviderDigitalOcean is a structure containing the DNS configuration for DigitalOcean Domains

        +
        + + + + + + + + + + + + + +
        FieldDescription
        + tokenSecretRef +
        + + SecretKeySelector + +
        +

        ACMEIssuerDNS01ProviderRFC2136

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        ACMEIssuerDNS01ProviderRFC2136 is a structure containing the configuration for RFC2136 DNS

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + nameserver +
        + string +
        +

        The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required.

        +
        + tsigSecretSecretRef +
        + + SecretKeySelector + +
        + (Optional) +

        The name of the secret containing the TSIG value. If tsigKeyName is defined, this field is required.

        +
        + tsigKeyName +
        + string +
        + (Optional) +

        The TSIG Key name configured in the DNS. If tsigSecretSecretRef is defined, this field is required.

        +
        + tsigAlgorithm +
        + string +
        + (Optional) +

        The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when tsigSecretSecretRef and tsigKeyName are defined. Supported values are (case-insensitive): HMACMD5 (default), HMACSHA1, HMACSHA256 or HMACSHA512.

        +
        +

        ACMEIssuerDNS01ProviderRoute53

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 configuration for AWS

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + accessKeyID +
        + string +
        + (Optional) +

        The AccessKeyID is used for authentication. Cannot be set when SecretAccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials

        +
        + accessKeyIDSecretRef +
        + + SecretKeySelector + +
        + (Optional) +

        The SecretAccessKey is used for authentication. If set, pull the AWS access key ID from a key within a Kubernetes Secret. Cannot be set when AccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials

        +
        + secretAccessKeySecretRef +
        + + SecretKeySelector + +
        + (Optional) +

        The SecretAccessKey is used for authentication. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials

        +
        + role +
        + string +
        + (Optional) +

        Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata

        +
        + hostedZoneID +
        + string +
        + (Optional) +

        If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call.

        +
        + region +
        + string +
        +

        Always set the region when using AccessKeyID and SecretAccessKey

        +
        +

        ACMEIssuerDNS01ProviderWebhook

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        ACMEIssuerDNS01ProviderWebhook specifies configuration for a webhook DNS01 provider, including where to POST ChallengePayload resources.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + groupName +
        + string +
        +

        The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation.

        +
        + solverName +
        + string +
        +

        The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. ‘cloudflare’.

        +
        + config +
        + + Kubernetes apiextensions/v1.JSON + +
        + (Optional) +

        Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation’s documentation.

        +
        +

        ACMEIssuerStatus

        +

        (Appears on: IssuerStatus)

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + uri +
        + string +
        + (Optional) +

        URI is the unique account identifier, which can also be used to retrieve account details from the CA

        +
        + lastRegisteredEmail +
        + string +
        + (Optional) +

        LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer

        +
        + lastPrivateKeyHash +
        + string +
        + (Optional) +

        LastPrivateKeyHash is a hash of the private key associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer

        +
        +

        AzureDNSEnvironment (string alias)

        +

        (Appears on: ACMEIssuerDNS01ProviderAzureDNS)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "AzureChinaCloud"

        +
        +

        "AzureGermanCloud"

        +
        +

        "AzurePublicCloud"

        +
        +

        "AzureUSGovernmentCloud"

        +
        +

        AzureManagedIdentity

        +

        (Appears on: ACMEIssuerDNS01ProviderAzureDNS)

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + clientID +
        + string +
        + (Optional) +

        client ID of the managed identity, can not be used at the same time as resourceID

        +
        + resourceID +
        + string +
        + (Optional) +

        resource ID of the managed identity, can not be used at the same time as clientID

        +
        +

        CNAMEStrategy (string alias)

        +

        (Appears on: ACMEChallengeSolverDNS01)

        +
        +

        CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. By default, the None strategy will be applied (i.e. do not follow CNAMEs).

        +
        +

        CertificateDNSNameSelector

        +

        (Appears on: ACMEChallengeSolver)

        +
        +

        CertificateDNSNameSelector selects certificates using a label selector, and can optionally select individual DNS names within those certificates. If both MatchLabels and DNSNames are empty, this selector will match all certificates and DNS names within them.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + matchLabels +
        + map[string]string +
        + (Optional) +

        A label selector that is used to refine the set of certificate’s that this challenge solver will apply to.

        +
        + dnsNames +
        + []string +
        + (Optional) +

        List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected.

        +
        + dnsZones +
        + []string +
        + (Optional) +

        List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected.

        +
        +

        ChallengeSpec

        +

        (Appears on: Challenge)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + url +
        + string +
        +

        The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge.

        +
        + authorizationURL +
        + string +
        +

        The URL to the ACME Authorization resource that this challenge is a part of.

        +
        + dnsName +
        + string +
        +

        dnsName is the identifier that this challenge is for, e.g. example.com. If the requested DNSName is a ‘wildcard’, this field MUST be set to the non-wildcard domain, e.g. for *.example.com, it must be example.com.

        +
        + wildcard +
        + bool +
        + (Optional) +

        wildcard will be true if this challenge is for a wildcard identifier, for example ‘*.example.com’.

        +
        + type +
        + + ACMEChallengeType + +
        +

        The type of ACME challenge this resource represents. One of “HTTP-01” or “DNS-01”.

        +
        + token +
        + string +
        +

        The ACME challenge token for this challenge. This is the raw value returned from the ACME server.

        +
        + key +
        + string +
        +

        + The ACME challenge key for this challenge For HTTP01 challenges, this is the value that must be responded with to complete the HTTP01 challenge in the format: + <private key JWK thumbprint>.<key from acme server for challenge>. For DNS01 challenges, this is the base64 encoded SHA256 sum of the + <private key JWK thumbprint>.<key from acme server for challenge> + text that must be set as the TXT record content. +

        +
        + solver +
        + + ACMEChallengeSolver + +
        +

        Contains the domain solving configuration that should be used to solve this challenge resource.

        +
        + issuerRef +
        + + ObjectReference + +
        +

        References a properly configured ACME-type Issuer which should be used to create this Challenge. If the Issuer does not exist, processing will be retried. If the Issuer is not an ‘ACME’ Issuer, an error will be returned and the Challenge will be marked as failed.

        +
        +

        ChallengeStatus

        +

        (Appears on: Challenge)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + processing +
        + bool +
        + (Optional) +

        Used to denote whether this challenge should be processed or not. This field will only be set to true by the ‘scheduling’ component. It will only be set to false by the ‘challenges’ controller, after the challenge has reached a final state or timed out. If this field is set to false, the challenge controller will not take any more action.

        +
        + presented +
        + bool +
        + (Optional) +

        presented will be set to true if the challenge values for this challenge are currently ‘presented’. This does not imply the self check is passing. Only that the values have been ‘submitted’ for the appropriate challenge mechanism (i.e. the DNS01 TXT record has been presented, or the HTTP01 configuration has been configured).

        +
        + reason +
        + string +
        + (Optional) +

        Contains human readable information on why the Challenge is in the current state.

        +
        + state +
        + + State + +
        + (Optional) +

        Contains the current ‘state’ of the challenge. If not set, the state of the challenge is unknown.

        +
        +

        HMACKeyAlgorithm (string alias)

        +

        (Appears on: ACMEExternalAccountBinding)

        +
        +

        HMACKeyAlgorithm is the name of a key algorithm used for HMAC encryption

        +
        + + + + + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "HS256"

        +
        +

        "HS384"

        +
        +

        "HS512"

        +
        +

        OrderSpec

        +

        (Appears on: Order)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + request +
        + []byte +
        +

        Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order.

        +
        + issuerRef +
        + + ObjectReference + +
        +

        IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an ‘ACME’ Issuer, an error will be returned and the Order will be marked as failed.

        +
        + commonName +
        + string +
        + (Optional) +

        CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in dnsNames or ipAddresses. This field must match the corresponding field on the DER encoded CSR.

        +
        + dnsNames +
        + []string +
        + (Optional) +

        DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.

        +
        + ipAddresses +
        + []string +
        + (Optional) +

        IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.

        +
        + duration +
        + + Kubernetes meta/v1.Duration + +
        + (Optional) +

        Duration is the duration for the not after date for the requested certificate. this is set on order creation as pe the ACME spec.

        +
        +

        OrderStatus

        +

        (Appears on: Order)

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + url +
        + string +
        + (Optional) +

        URL of the Order. This will initially be empty when the resource is first created. The Order controller will populate this field when the Order is first processed. This field will be immutable after it is initially set.

        +
        + finalizeURL +
        + string +
        + (Optional) +

        FinalizeURL of the Order. This is used to obtain certificates for this order once it has been completed.

        +
        + authorizations +
        + + []ACMEAuthorization + +
        + (Optional) +

        Authorizations contains data returned from the ACME server on what authorizations must be completed in order to validate the DNS names specified on the Order.

        +
        + certificate +
        + []byte +
        + (Optional) +

        Certificate is a copy of the PEM encoded certificate for this Order. This field will be populated after the order has been successfully finalized with the ACME server, and the order has transitioned to the ‘valid’ state.

        +
        + state +
        + + State + +
        + (Optional) +

        State contains the current state of this Order resource. States ‘success’ and ‘expired’ are ‘final’

        +
        + reason +
        + string +
        + (Optional) +

        Reason optionally provides more information about a why the order is in the current state.

        +
        + failureTime +
        + + Kubernetes meta/v1.Time + +
        + (Optional) +

        FailureTime stores the time that this order failed. This is used to influence garbage collection and back-off.

        +
        +

        State (string alias)

        +

        (Appears on: ACMEAuthorization, ChallengeStatus, OrderStatus)

        +
        +

        + State represents the state of an ACME resource, such as an Order. The possible options here map to the corresponding values in the ACME specification. Full details of these values can be found here: https://tools.ietf.org/html/draft-ietf-acme-acme-15#section-7.1.6 + Clients utilising this type must also gracefully handle unknown values, as the contents of this enumeration may be added to over time. +

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "errored"

        +
        +

        Errored signifies that the ACME resource has errored for some reason. This is a catch-all state, and is used for marking internal cert-manager errors such as validation failures. This is a final state.

        +
        +

        "expired"

        +
        +

        Expired signifies that an ACME resource has expired. If an Order is marked ‘Expired’, one of its validations may have expired or the Order itself. This is a final state.

        +
        +

        "invalid"

        +
        +

        Invalid signifies that an ACME resource is invalid for some reason. If an Order is marked ‘invalid’, one of its validations be have invalid for some reason. This is a final state.

        +
        +

        "pending"

        +
        +

        Pending signifies that an ACME resource is still pending and is not yet ready. If an Order is marked ‘Pending’, the validations for that Order are still in progress. This is a transient state.

        +
        +

        "processing"

        +
        +

        Processing signifies that an ACME resource is being processed by the server. If an Order is marked ‘Processing’, the validations for that Order are currently being processed. This is a transient state.

        +
        +

        "ready"

        +
        +

        Ready signifies that an ACME resource is in a ready state. If an order is ‘ready’, all of its challenges have been completed successfully and the order is ready to be finalized. Once finalized, it will transition to the Valid state. This is a transient state.

        +
        +

        ""

        +
        +

        Unknown is not a real state as part of the ACME spec. It is used to represent an unrecognised value.

        +
        +

        "valid"

        +
        +

        Valid signifies that an ACME resource is in a valid state. If an order is ‘valid’, it has been finalized with the ACME server and the certificate can be retrieved from the ACME server using the certificate URL stored in the Order’s status subresource. This is a final state.

        +
        +
        +

        cert-manager.io/v1

        +
        +

        Package v1 is the v1 version of the API.

        +
        +

        Resource Types:

        + +

        Certificate

        +
        +

        A Certificate resource should be created to ensure an up to date and signed X.509 certificate is stored in the Kubernetes Secret resource named in spec.secretName.

        +

        The stored certificate will be renewed before it expires (as configured by spec.renewBefore).

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + apiVersion +
        + string +
        + cert-manager.io/v1 +
        + kind +
        + string +
        + Certificate +
        + metadata +
        + + Kubernetes meta/v1.ObjectMeta + +
        + (Optional) +

        Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

        + Refer to the Kubernetes API documentation for the fields of the + metadata field. +
        + spec +
        + + CertificateSpec + +
        + (Optional) +

        + Specification of the desired state of the Certificate resource. + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status +

        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + subject +
        + + X509Subject + +
        + (Optional) +

        Requested set of X509 certificate subject attributes. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6

        +

        The common name attribute is specified separately in the commonName field. Cannot be set if the literalSubject field is set.

        +
        + literalSubject +
        + string +
        + (Optional) +

        Requested X.509 certificate subject, represented using the LDAP “String Representation of a Distinguished Name” [1]. Important: the LDAP string format also specifies the order of the attributes in the subject, this is important when issuing certs for LDAP authentication. Example: CN=foo,DC=corp,DC=example,DC=com More info [1]: https://datatracker.ietf.org/doc/html/rfc4514 More info: https://github.com/cert-manager/cert-manager/issues/3203 More info: https://github.com/cert-manager/cert-manager/issues/4424

        +

        Cannot be set if the subject or commonName field is set. This is an Alpha Feature and is only enabled with the --feature-gates=LiteralCertificateSubject=true option set on both the controller and webhook components.

        +
        + commonName +
        + string +
        + (Optional) +

        Requested common name X509 certificate subject attribute. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 NOTE: TLS clients will ignore this value when any subject alternative name is set (see https://tools.ietf.org/html/rfc6125#section-6.4.4).

        +

        Should have a length of 64 characters or fewer to avoid generating invalid CSRs. Cannot be set if the literalSubject field is set.

        +
        + duration +
        + + Kubernetes meta/v1.Duration + +
        + (Optional) +

        Requested ‘duration’ (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute.

        +

        If unset, this defaults to 90 days. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration.

        +
        + renewBefore +
        + + Kubernetes meta/v1.Duration + +
        + (Optional) +

        How long before the currently issued certificate’s expiry cert-manager should renew the certificate. For example, if a certificate is valid for 60 minutes, and renewBefore=10m, cert-manager will begin to attempt to renew the certificate 50 minutes after it was issued (i.e. when there are 10 minutes remaining until the certificate is no longer valid).

        +

        NOTE: The actual lifetime of the issued certificate is used to determine the renewal time. If an issuer returns a certificate with a different lifetime than the one requested, cert-manager will use the lifetime of the issued certificate.

        +

        If unset, this defaults to 13 of the issued certificate’s lifetime. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration.

        +
        + dnsNames +
        + []string +
        + (Optional) +

        Requested DNS subject alternative names.

        +
        + ipAddresses +
        + []string +
        + (Optional) +

        Requested IP address subject alternative names.

        +
        + uris +
        + []string +
        + (Optional) +

        Requested URI subject alternative names.

        +
        + emailAddresses +
        + []string +
        + (Optional) +

        Requested email subject alternative names.

        +
        + secretName +
        + string +
        +

        Name of the Secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer. The Secret resource lives in the same namespace as the Certificate resource.

        +
        + secretTemplate +
        + + CertificateSecretTemplate + +
        + (Optional) +

        Defines annotations and labels to be copied to the Certificate’s Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate’s Secret.

        +
        + keystores +
        + + CertificateKeystores + +
        + (Optional) +

        Additional keystore output formats to be stored in the Certificate’s Secret.

        +
        + issuerRef +
        + + ObjectReference + +
        +

        Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace.

        +

        The name field of the reference must always be specified.

        +
        + isCA +
        + bool +
        + (Optional) +

        Requested basic constraints isCA value. The isCA value is used to set the isCA field on the created CertificateRequest resources. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute.

        +

        If true, this will automatically add the cert sign usage to the list of requested usages.

        +
        + usages +
        + + []KeyUsage + +
        + (Optional) +

        Requested key usages and extended key usages. These usages are used to set the usages field on the created CertificateRequest resources. If encodeUsagesInRequest is unset or set to true, the usages will additionally be encoded in the request field which contains the CSR blob.

        +

        If unset, defaults to digital signature and key encipherment.

        +
        + privateKey +
        + + CertificatePrivateKey + +
        + (Optional) +

        Private key options. These include the key algorithm and size, the used encoding and the rotation policy.

        +
        + encodeUsagesInRequest +
        + bool +
        + (Optional) +

        Whether the KeyUsage and ExtKeyUsage extensions should be set in the encoded CSR.

        +

        This option defaults to true, and should only be disabled if the target issuer does not support CSRs with these X509 KeyUsage/ ExtKeyUsage extensions.

        +
        + revisionHistoryLimit +
        + int32 +
        + (Optional) +

        + The maximum number of CertificateRequest revisions that are maintained in the Certificate’s history. Each revision represents a single CertificateRequest + created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. +

        +

        If set, revisionHistoryLimit must be a value of 1 or greater. If unset (nil), revisions will not be garbage collected. Default value is nil.

        +
        + additionalOutputFormats +
        + + []CertificateAdditionalOutputFormat + +
        + (Optional) +

        Defines extra output formats of the private key and signed certificate chain to be written to this Certificate’s target Secret.

        +

        + This is an Alpha Feature and is only enabled with the + --feature-gates=AdditionalCertificateOutputFormats=true option set on both the controller and webhook components. +

        +
        +
        + status +
        + + CertificateStatus + +
        + (Optional) +

        Status of the Certificate. This is set and managed automatically. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

        +
        +

        CertificateRequest

        +
        +

        A CertificateRequest is used to request a signed certificate from one of the configured issuers.

        +

        All fields within the CertificateRequest’s spec are immutable after creation. A CertificateRequest will either succeed or fail, as denoted by its Ready status condition and its status.failureTime field.

        +

        A CertificateRequest is a one-shot resource, meaning it represents a single point in time request for a certificate and cannot be re-used.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + apiVersion +
        + string +
        + cert-manager.io/v1 +
        + kind +
        + string +
        + CertificateRequest +
        + metadata +
        + + Kubernetes meta/v1.ObjectMeta + +
        + (Optional) +

        Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

        + Refer to the Kubernetes API documentation for the fields of the + metadata field. +
        + spec +
        + + CertificateRequestSpec + +
        + (Optional) +

        + Specification of the desired state of the CertificateRequest resource. + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status +

        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + duration +
        + + Kubernetes meta/v1.Duration + +
        + (Optional) +

        Requested ‘duration’ (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute.

        +
        + issuerRef +
        + + ObjectReference + +
        +

        Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace.

        +

        The name field of the reference must always be specified.

        +
        + request +
        + []byte +
        +

        The PEM-encoded X.509 certificate signing request to be submitted to the issuer for signing.

        +

        If the CSR has a BasicConstraints extension, its isCA attribute must match the isCA value of this CertificateRequest. If the CSR has a KeyUsage extension, its key usages must match the key usages in the usages field of this CertificateRequest. If the CSR has a ExtKeyUsage extension, its extended key usages must match the extended key usages in the usages field of this CertificateRequest.

        +
        + isCA +
        + bool +
        + (Optional) +

        Requested basic constraints isCA value. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute.

        +

        NOTE: If the CSR in the Request field has a BasicConstraints extension, it must have the same isCA value as specified here.

        +

        If true, this will automatically add the cert sign usage to the list of requested usages.

        +
        + usages +
        + + []KeyUsage + +
        + (Optional) +

        Requested key usages and extended key usages.

        +

        NOTE: If the CSR in the Request field has uses the KeyUsage or ExtKeyUsage extension, these extensions must have the same values as specified here without any additional values.

        +

        If unset, defaults to digital signature and key encipherment.

        +
        + username +
        + string +
        + (Optional) +

        Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

        +
        + uid +
        + string +
        + (Optional) +

        UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

        +
        + groups +
        + []string +
        + (Optional) +

        Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

        +
        + extra +
        + map[string][]string +
        + (Optional) +

        Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

        +
        +
        + status +
        + + CertificateRequestStatus + +
        + (Optional) +

        Status of the CertificateRequest. This is set and managed automatically. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

        +
        +

        ClusterIssuer

        +
        +

        A ClusterIssuer represents a certificate issuing authority which can be referenced as part of issuerRef fields. It is similar to an Issuer, however it is cluster-scoped and therefore can be referenced by resources that exist in any namespace, not just the same namespace as the referent.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + apiVersion +
        + string +
        + cert-manager.io/v1 +
        + kind +
        + string +
        + ClusterIssuer +
        + metadata +
        + + Kubernetes meta/v1.ObjectMeta + +
        + Refer to the Kubernetes API documentation for the fields of the + metadata field. +
        + spec +
        + + IssuerSpec + +
        +

        Desired state of the ClusterIssuer resource.

        +
        +
        + + + + + +
        + IssuerConfig +
        + + IssuerConfig + +
        +

        (Members of IssuerConfig are embedded into this type.)

        +
        +
        + status +
        + + IssuerStatus + +
        + (Optional) +

        Status of the ClusterIssuer. This is set and managed automatically.

        +
        +

        Issuer

        +
        +

        An Issuer represents a certificate issuing authority which can be referenced as part of issuerRef fields. It is scoped to a single namespace and can therefore only be referenced by resources within the same namespace.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + apiVersion +
        + string +
        + cert-manager.io/v1 +
        + kind +
        + string +
        + Issuer +
        + metadata +
        + + Kubernetes meta/v1.ObjectMeta + +
        + Refer to the Kubernetes API documentation for the fields of the + metadata field. +
        + spec +
        + + IssuerSpec + +
        +

        Desired state of the Issuer resource.

        +
        +
        + + + + + +
        + IssuerConfig +
        + + IssuerConfig + +
        +

        (Members of IssuerConfig are embedded into this type.)

        +
        +
        + status +
        + + IssuerStatus + +
        + (Optional) +

        Status of the Issuer. This is set and managed automatically.

        +
        +

        CAIssuer

        +

        (Appears on: IssuerConfig)

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + secretName +
        + string +
        +

        SecretName is the name of the secret used to sign Certificates issued by this Issuer.

        +
        + crlDistributionPoints +
        + []string +
        + (Optional) +

        The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set, certificates will be issued without distribution points set.

        +
        + ocspServers +
        + []string +
        + (Optional) +

        The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be “http://ocsp.int-x3.letsencrypt.org”.

        +
        +

        CertificateAdditionalOutputFormat

        +

        (Appears on: CertificateSpec)

        +
        +

        CertificateAdditionalOutputFormat defines an additional output format of a Certificate resource. These contain supplementary data formats of the signed certificate chain and paired private key.

        +
        + + + + + + + + + + + + + +
        FieldDescription
        + type +
        + + CertificateOutputFormatType + +
        +

        Type is the name of the format type that should be written to the Certificate’s target Secret.

        +
        +

        CertificateCondition

        +

        (Appears on: CertificateStatus)

        +
        +

        CertificateCondition contains condition information for an Certificate.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + type +
        + + CertificateConditionType + +
        +

        Type of the condition, known values are (Ready, Issuing).

        +
        + status +
        + + ConditionStatus + +
        +

        Status of the condition, one of (True, False, Unknown).

        +
        + lastTransitionTime +
        + + Kubernetes meta/v1.Time + +
        + (Optional) +

        LastTransitionTime is the timestamp corresponding to the last status change of this condition.

        +
        + reason +
        + string +
        + (Optional) +

        Reason is a brief machine readable explanation for the condition’s last transition.

        +
        + message +
        + string +
        + (Optional) +

        Message is a human readable description of the details of the last transition, complementing reason.

        +
        + observedGeneration +
        + int64 +
        + (Optional) +

        If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Certificate.

        +
        +

        CertificateConditionType (string alias)

        +

        (Appears on: CertificateCondition)

        +
        +

        CertificateConditionType represents an Certificate condition value.

        +
        + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "Issuing"

        +
        +

        + A condition added to Certificate resources when an issuance is required. This condition will be automatically added and set to true if: * No keypair data exists in the target Secret * The data stored in the Secret cannot be decoded * The private key and certificate do not have matching public keys * If a CertificateRequest for the current revision exists and the certificate data stored in the Secret does not match the + status.certificate on the CertificateRequest. * If no CertificateRequest resource exists for the current revision, the options on the Certificate resource are compared against the X.509 data in the Secret, similar to what’s done in earlier versions. If there is a mismatch, an issuance is triggered. This condition may also be added by external API consumers to trigger a re-issuance manually for any other reason. +

        +

        It will be removed by the ‘issuing’ controller upon completing issuance.

        +
        +

        "Ready"

        +
        +

        CertificateConditionReady indicates that a certificate is ready for use. This is defined as: - The target secret exists - The target secret contains a certificate that has not expired - The target secret contains a private key valid for the certificate - The commonName and dnsNames attributes match those specified on the Certificate

        +
        +

        CertificateKeystores

        +

        (Appears on: CertificateSpec)

        +
        +

        CertificateKeystores configures additional keystore output formats to be created in the Certificate’s output Secret.

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + jks +
        + + JKSKeystore + +
        + (Optional) +

        + JKS configures options for storing a JKS keystore in the + spec.secretName Secret resource. +

        +
        + pkcs12 +
        + + PKCS12Keystore + +
        + (Optional) +

        + PKCS12 configures options for storing a PKCS12 keystore in the + spec.secretName Secret resource. +

        +
        +

        CertificateOutputFormatType (string alias)

        +

        (Appears on: CertificateAdditionalOutputFormat)

        +
        +

        + CertificateOutputFormatType specifies which additional output formats should be written to the Certificate’s target Secret. Allowed values are DER or CombinedPEM. When Type is set to DER an additional entry key.der will be written to the Secret, containing the binary format of the private key. When Type is set to CombinedPEM an additional entry tls-combined.pem + will be written to the Secret, containing the PEM formatted private key and signed certificate chain (tls.key + tls.crt concatenated). +

        +
        + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "CombinedPEM"

        +
        +

        + CertificateOutputFormatCombinedPEM writes the Certificate’s signed certificate chain and private key, in PEM format, to the + tls-combined.pem target Secret Data key. The value at this key will include the private key PEM document, followed by at least one new line character, followed by the chain of signed certificate PEM documents (<private key> + \n + <signed certificate chain>). +

        +
        +

        "DER"

        +
        +

        CertificateOutputFormatDER writes the Certificate’s private key in DER binary format to the key.der target Secret Data key.

        +
        +

        CertificatePrivateKey

        +

        (Appears on: CertificateSpec)

        +
        +

        CertificatePrivateKey contains configuration options for private keys used by the Certificate controller. These include the key algorithm and size, the used encoding and the rotation policy.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + rotationPolicy +
        + + PrivateKeyRotationPolicy + +
        + (Optional) +

        RotationPolicy controls how private keys should be regenerated when a re-issuance is being processed.

        +

        If set to Never, a private key will only be generated if one does not already exist in the target spec.secretName. If one does exists but it does not have the correct algorithm or size, a warning will be raised to await user intervention. If set to Always, a private key matching the specified requirements will be generated whenever a re-issuance occurs. Default is Never for backward compatibility.

        +
        + encoding +
        + + PrivateKeyEncoding + +
        + (Optional) +

        The private key cryptography standards (PKCS) encoding for this certificate’s private key to be encoded in.

        +

        If provided, allowed values are PKCS1 and PKCS8 standing for PKCS#1 and PKCS#8, respectively. Defaults to PKCS1 if not specified.

        +
        + algorithm +
        + + PrivateKeyAlgorithm + +
        + (Optional) +

        Algorithm is the private key algorithm of the corresponding private key for this certificate.

        +

        If provided, allowed values are either RSA, ECDSA or Ed25519. If algorithm is specified and size is not provided, key size of 2048 will be used for RSA key algorithm and key size of 256 will be used for ECDSA key algorithm. key size is ignored when using the Ed25519 key algorithm.

        +
        + size +
        + int +
        + (Optional) +

        Size is the key bit size of the corresponding private key for this certificate.

        +

        If algorithm is set to RSA, valid values are 2048, 4096 or 8192, and will default to 2048 if not specified. If algorithm is set to ECDSA, valid values are 256, 384 or 521, and will default to 256 if not specified. If algorithm is set to Ed25519, Size is ignored. No other values are allowed.

        +
        +

        CertificateRequestCondition

        +

        (Appears on: CertificateRequestStatus)

        +
        +

        CertificateRequestCondition contains condition information for a CertificateRequest.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + type +
        + + CertificateRequestConditionType + +
        +

        Type of the condition, known values are (Ready, InvalidRequest,Approved, Denied).

        +
        + status +
        + + ConditionStatus + +
        +

        Status of the condition, one of (True, False, Unknown).

        +
        + lastTransitionTime +
        + + Kubernetes meta/v1.Time + +
        + (Optional) +

        LastTransitionTime is the timestamp corresponding to the last status change of this condition.

        +
        + reason +
        + string +
        + (Optional) +

        Reason is a brief machine readable explanation for the condition’s last transition.

        +
        + message +
        + string +
        + (Optional) +

        Message is a human readable description of the details of the last transition, complementing reason.

        +
        +

        CertificateRequestConditionType (string alias)

        +

        (Appears on: CertificateRequestCondition)

        +
        +

        CertificateRequestConditionType represents an Certificate condition value.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "Approved"

        +
        +

        + CertificateRequestConditionApproved indicates that a certificate request is approved and ready for signing. Condition must never have a status of + False, and cannot be modified once set. Cannot be set alongside Denied. +

        +
        +

        "Denied"

        +
        +

        + CertificateRequestConditionDenied indicates that a certificate request is denied, and must never be signed. Condition must never have a status of + False, and cannot be modified once set. Cannot be set alongside Approved. +

        +
        +

        "InvalidRequest"

        +
        +

        CertificateRequestConditionInvalidRequest indicates that a certificate signer has refused to sign the request due to at least one of the input parameters being invalid. Additional information about why the request was rejected can be found in the reason and message fields.

        +
        +

        "Ready"

        +
        +

        CertificateRequestConditionReady indicates that a certificate is ready for use. This is defined as: - The target certificate exists in CertificateRequest.Status

        +
        +

        CertificateRequestSpec

        +

        (Appears on: CertificateRequest)

        +
        +

        CertificateRequestSpec defines the desired state of CertificateRequest

        +

        NOTE: It is important to note that the issuer can choose to ignore or change any of the requested attributes. How the issuer maps a certificate request to a signed certificate is the full responsibility of the issuer itself. For example, as an edge case, an issuer that inverts the isCA value is free to do so.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + duration +
        + + Kubernetes meta/v1.Duration + +
        + (Optional) +

        Requested ‘duration’ (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute.

        +
        + issuerRef +
        + + ObjectReference + +
        +

        Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace.

        +

        The name field of the reference must always be specified.

        +
        + request +
        + []byte +
        +

        The PEM-encoded X.509 certificate signing request to be submitted to the issuer for signing.

        +

        If the CSR has a BasicConstraints extension, its isCA attribute must match the isCA value of this CertificateRequest. If the CSR has a KeyUsage extension, its key usages must match the key usages in the usages field of this CertificateRequest. If the CSR has a ExtKeyUsage extension, its extended key usages must match the extended key usages in the usages field of this CertificateRequest.

        +
        + isCA +
        + bool +
        + (Optional) +

        Requested basic constraints isCA value. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute.

        +

        NOTE: If the CSR in the Request field has a BasicConstraints extension, it must have the same isCA value as specified here.

        +

        If true, this will automatically add the cert sign usage to the list of requested usages.

        +
        + usages +
        + + []KeyUsage + +
        + (Optional) +

        Requested key usages and extended key usages.

        +

        NOTE: If the CSR in the Request field has uses the KeyUsage or ExtKeyUsage extension, these extensions must have the same values as specified here without any additional values.

        +

        If unset, defaults to digital signature and key encipherment.

        +
        + username +
        + string +
        + (Optional) +

        Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

        +
        + uid +
        + string +
        + (Optional) +

        UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

        +
        + groups +
        + []string +
        + (Optional) +

        Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

        +
        + extra +
        + map[string][]string +
        + (Optional) +

        Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.

        +
        +

        CertificateRequestStatus

        +

        (Appears on: CertificateRequest)

        +
        +

        CertificateRequestStatus defines the observed state of CertificateRequest and resulting signed certificate.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + conditions +
        + + []CertificateRequestCondition + +
        + (Optional) +

        List of status conditions to indicate the status of a CertificateRequest. Known condition types are Ready, InvalidRequest, Approved and Denied.

        +
        + certificate +
        + []byte +
        + (Optional) +

        + The PEM encoded X.509 certificate resulting from the certificate signing request. If not set, the CertificateRequest has either not been completed or has failed. More information on failure can be found by checking the + conditions field. +

        +
        + ca +
        + []byte +
        + (Optional) +

        The PEM encoded X.509 certificate of the signer, also known as the CA (Certificate Authority). This is set on a best-effort basis by different issuers. If not set, the CA is assumed to be unknown/not available.

        +
        + failureTime +
        + + Kubernetes meta/v1.Time + +
        + (Optional) +

        FailureTime stores the time that this CertificateRequest failed. This is used to influence garbage collection and back-off.

        +
        +

        CertificateSecretTemplate

        +

        (Appears on: CertificateSpec)

        +
        +

        CertificateSecretTemplate defines the default labels and annotations to be copied to the Kubernetes Secret resource named in CertificateSpec.secretName.

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + annotations +
        + map[string]string +
        + (Optional) +

        Annotations is a key value map to be copied to the target Kubernetes Secret.

        +
        + labels +
        + map[string]string +
        + (Optional) +

        Labels is a key value map to be copied to the target Kubernetes Secret.

        +
        +

        CertificateSpec

        +

        (Appears on: Certificate)

        +
        +

        CertificateSpec defines the desired state of Certificate.

        +

        NOTE: The specification contains a lot of “requested” certificate attributes, it is important to note that the issuer can choose to ignore or change any of these requested attributes. How the issuer maps a certificate request to a signed certificate is the full responsibility of the issuer itself. For example, as an edge case, an issuer that inverts the isCA value is free to do so.

        +

        A valid Certificate requires at least one of a CommonName, LiteralSubject, DNSName, or URI to be valid.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + subject +
        + + X509Subject + +
        + (Optional) +

        Requested set of X509 certificate subject attributes. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6

        +

        The common name attribute is specified separately in the commonName field. Cannot be set if the literalSubject field is set.

        +
        + literalSubject +
        + string +
        + (Optional) +

        Requested X.509 certificate subject, represented using the LDAP “String Representation of a Distinguished Name” [1]. Important: the LDAP string format also specifies the order of the attributes in the subject, this is important when issuing certs for LDAP authentication. Example: CN=foo,DC=corp,DC=example,DC=com More info [1]: https://datatracker.ietf.org/doc/html/rfc4514 More info: https://github.com/cert-manager/cert-manager/issues/3203 More info: https://github.com/cert-manager/cert-manager/issues/4424

        +

        Cannot be set if the subject or commonName field is set. This is an Alpha Feature and is only enabled with the --feature-gates=LiteralCertificateSubject=true option set on both the controller and webhook components.

        +
        + commonName +
        + string +
        + (Optional) +

        Requested common name X509 certificate subject attribute. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 NOTE: TLS clients will ignore this value when any subject alternative name is set (see https://tools.ietf.org/html/rfc6125#section-6.4.4).

        +

        Should have a length of 64 characters or fewer to avoid generating invalid CSRs. Cannot be set if the literalSubject field is set.

        +
        + duration +
        + + Kubernetes meta/v1.Duration + +
        + (Optional) +

        Requested ‘duration’ (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute.

        +

        If unset, this defaults to 90 days. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration.

        +
        + renewBefore +
        + + Kubernetes meta/v1.Duration + +
        + (Optional) +

        How long before the currently issued certificate’s expiry cert-manager should renew the certificate. For example, if a certificate is valid for 60 minutes, and renewBefore=10m, cert-manager will begin to attempt to renew the certificate 50 minutes after it was issued (i.e. when there are 10 minutes remaining until the certificate is no longer valid).

        +

        NOTE: The actual lifetime of the issued certificate is used to determine the renewal time. If an issuer returns a certificate with a different lifetime than the one requested, cert-manager will use the lifetime of the issued certificate.

        +

        If unset, this defaults to 13 of the issued certificate’s lifetime. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration.

        +
        + dnsNames +
        + []string +
        + (Optional) +

        Requested DNS subject alternative names.

        +
        + ipAddresses +
        + []string +
        + (Optional) +

        Requested IP address subject alternative names.

        +
        + uris +
        + []string +
        + (Optional) +

        Requested URI subject alternative names.

        +
        + emailAddresses +
        + []string +
        + (Optional) +

        Requested email subject alternative names.

        +
        + secretName +
        + string +
        +

        Name of the Secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer. The Secret resource lives in the same namespace as the Certificate resource.

        +
        + secretTemplate +
        + + CertificateSecretTemplate + +
        + (Optional) +

        Defines annotations and labels to be copied to the Certificate’s Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate’s Secret.

        +
        + keystores +
        + + CertificateKeystores + +
        + (Optional) +

        Additional keystore output formats to be stored in the Certificate’s Secret.

        +
        + issuerRef +
        + + ObjectReference + +
        +

        Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace.

        +

        The name field of the reference must always be specified.

        +
        + isCA +
        + bool +
        + (Optional) +

        Requested basic constraints isCA value. The isCA value is used to set the isCA field on the created CertificateRequest resources. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute.

        +

        If true, this will automatically add the cert sign usage to the list of requested usages.

        +
        + usages +
        + + []KeyUsage + +
        + (Optional) +

        Requested key usages and extended key usages. These usages are used to set the usages field on the created CertificateRequest resources. If encodeUsagesInRequest is unset or set to true, the usages will additionally be encoded in the request field which contains the CSR blob.

        +

        If unset, defaults to digital signature and key encipherment.

        +
        + privateKey +
        + + CertificatePrivateKey + +
        + (Optional) +

        Private key options. These include the key algorithm and size, the used encoding and the rotation policy.

        +
        + encodeUsagesInRequest +
        + bool +
        + (Optional) +

        Whether the KeyUsage and ExtKeyUsage extensions should be set in the encoded CSR.

        +

        This option defaults to true, and should only be disabled if the target issuer does not support CSRs with these X509 KeyUsage/ ExtKeyUsage extensions.

        +
        + revisionHistoryLimit +
        + int32 +
        + (Optional) +

        + The maximum number of CertificateRequest revisions that are maintained in the Certificate’s history. Each revision represents a single CertificateRequest + created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. +

        +

        If set, revisionHistoryLimit must be a value of 1 or greater. If unset (nil), revisions will not be garbage collected. Default value is nil.

        +
        + additionalOutputFormats +
        + + []CertificateAdditionalOutputFormat + +
        + (Optional) +

        Defines extra output formats of the private key and signed certificate chain to be written to this Certificate’s target Secret.

        +

        + This is an Alpha Feature and is only enabled with the + --feature-gates=AdditionalCertificateOutputFormats=true option set on both the controller and webhook components. +

        +
        +

        CertificateStatus

        +

        (Appears on: Certificate)

        +
        +

        CertificateStatus defines the observed state of Certificate

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + conditions +
        + + []CertificateCondition + +
        + (Optional) +

        List of status conditions to indicate the status of certificates. Known condition types are Ready and Issuing.

        +
        + lastFailureTime +
        + + Kubernetes meta/v1.Time + +
        + (Optional) +

        LastFailureTime is set only if the lastest issuance for this Certificate failed and contains the time of the failure. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1). If the latest issuance has succeeded this field will be unset.

        +
        + notBefore +
        + + Kubernetes meta/v1.Time + +
        + (Optional) +

        The time after which the certificate stored in the secret named by this resource in spec.secretName is valid.

        +
        + notAfter +
        + + Kubernetes meta/v1.Time + +
        + (Optional) +

        The expiration time of the certificate stored in the secret named by this resource in spec.secretName.

        +
        + renewalTime +
        + + Kubernetes meta/v1.Time + +
        + (Optional) +

        RenewalTime is the time at which the certificate will be next renewed. If not set, no upcoming renewal is scheduled.

        +
        + revision +
        + int +
        + (Optional) +

        The current ‘revision’ of the certificate as issued.

        +

        + When a CertificateRequest resource is created, it will have the + cert-manager.io/certificate-revision set to one greater than the current value of this field. +

        +

        Upon issuance, this field will be set to the value of the annotation on the CertificateRequest resource used to issue the certificate.

        +

        Persisting the value on the CertificateRequest resource allows the certificates controller to know whether a request is part of an old issuance or if it is part of the ongoing revision’s issuance by checking if the revision value in the annotation is greater than this field.

        +
        + nextPrivateKeySecretName +
        + string +
        + (Optional) +

        + The name of the Secret resource containing the private key to be used for the next certificate iteration. The keymanager controller will automatically set this field if the + Issuing condition is set to True. It will automatically unset this field when the Issuing condition is not set or False. +

        +
        + failedIssuanceAttempts +
        + int +
        + (Optional) +

        The number of continuous failed issuance attempts up till now. This field gets removed (if set) on a successful issuance and gets set to 1 if unset and an issuance has failed. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1).

        +
        +

        GenericIssuer

        +
        +

        IssuerCondition

        +

        (Appears on: IssuerStatus)

        +
        +

        IssuerCondition contains condition information for an Issuer.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + type +
        + + IssuerConditionType + +
        +

        Type of the condition, known values are (Ready).

        +
        + status +
        + + ConditionStatus + +
        +

        Status of the condition, one of (True, False, Unknown).

        +
        + lastTransitionTime +
        + + Kubernetes meta/v1.Time + +
        + (Optional) +

        LastTransitionTime is the timestamp corresponding to the last status change of this condition.

        +
        + reason +
        + string +
        + (Optional) +

        Reason is a brief machine readable explanation for the condition’s last transition.

        +
        + message +
        + string +
        + (Optional) +

        Message is a human readable description of the details of the last transition, complementing reason.

        +
        + observedGeneration +
        + int64 +
        + (Optional) +

        If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer.

        +
        +

        IssuerConditionType (string alias)

        +

        (Appears on: IssuerCondition)

        +
        +

        IssuerConditionType represents an Issuer condition value.

        +
        + + + + + + + + + + + + + +
        ValueDescription
        +

        "Ready"

        +
        +

        IssuerConditionReady represents the fact that a given Issuer condition is in ready state and able to issue certificates. If the status of this condition is False, CertificateRequest controllers should prevent attempts to sign certificates.

        +
        +

        IssuerConfig

        +

        (Appears on: IssuerSpec)

        +
        +

        The configuration for the issuer. Only one of these can be set.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + acme +
        + + ACMEIssuer + +
        + (Optional) +

        ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates.

        +
        + ca +
        + + CAIssuer + +
        + (Optional) +

        CA configures this issuer to sign certificates using a signing CA keypair stored in a Secret resource. This is used to build internal PKIs that are managed by cert-manager.

        +
        + vault +
        + + VaultIssuer + +
        + (Optional) +

        Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend.

        +
        + selfSigned +
        + + SelfSignedIssuer + +
        + (Optional) +

        SelfSigned configures this issuer to ‘self sign’ certificates using the private key used to create the CertificateRequest object.

        +
        + venafi +
        + + VenafiIssuer + +
        + (Optional) +

        Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone.

        +
        +

        IssuerSpec

        +

        (Appears on: ClusterIssuer, Issuer)

        +
        +

        IssuerSpec is the specification of an Issuer. This includes any configuration required for the issuer.

        +
        + + + + + + + + + + + + + +
        FieldDescription
        + IssuerConfig +
        + + IssuerConfig + +
        +

        (Members of IssuerConfig are embedded into this type.)

        +
        +

        IssuerStatus

        +

        (Appears on: ClusterIssuer, Issuer)

        +
        +

        IssuerStatus contains status information about an Issuer

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + conditions +
        + + []IssuerCondition + +
        + (Optional) +

        List of status conditions to indicate the status of a CertificateRequest. Known condition types are Ready.

        +
        + acme +
        + + ACMEIssuerStatus + +
        + (Optional) +

        ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates.

        +
        +

        JKSKeystore

        +

        (Appears on: CertificateKeystores)

        +
        +

        + JKS configures options for storing a JKS keystore in the spec.secretName + Secret resource. +

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + create +
        + bool +
        +

        + Create enables JKS keystore creation for the Certificate. If true, a file named keystore.jks will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. If the issuer provided a CA certificate, a file named truststore.jks will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef + containing the issuing Certificate Authority +

        +
        + passwordSecretRef +
        + + SecretKeySelector + +
        +

        PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the JKS keystore.

        +
        +

        KeyUsage (string alias)

        +

        (Appears on: CertificateRequestSpec, CertificateSpec)

        +
        +

        + KeyUsage specifies valid usage contexts for keys. See: + https://tools.ietf.org/html/rfc5280#section-4.2.1.3 + https://tools.ietf.org/html/rfc5280#section-4.2.1.12 +

        +

        Valid KeyUsage values are as follows: “signing”, “digital signature”, “content commitment”, “key encipherment”, “key agreement”, “data encipherment”, “cert sign”, “crl sign”, “encipher only”, “decipher only”, “any”, “server auth”, “client auth”, “code signing”, “email protection”, “s/mime”, “ipsec end system”, “ipsec tunnel”, “ipsec user”, “timestamping”, “ocsp signing”, “microsoft sgc”, “netscape sgc”

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "any"

        +
        +

        "crl sign"

        +
        +

        "cert sign"

        +
        +

        "client auth"

        +
        +

        "code signing"

        +
        +

        "content commitment"

        +
        +

        "data encipherment"

        +
        +

        "decipher only"

        +
        +

        "digital signature"

        +
        +

        "email protection"

        +
        +

        "encipher only"

        +
        +

        "ipsec end system"

        +
        +

        "ipsec tunnel"

        +
        +

        "ipsec user"

        +
        +

        "key agreement"

        +
        +

        "key encipherment"

        +
        +

        "microsoft sgc"

        +
        +

        "netscape sgc"

        +
        +

        "ocsp signing"

        +
        +

        "s/mime"

        +
        +

        "server auth"

        +
        +

        "signing"

        +
        +

        "timestamping"

        +
        +

        PKCS12Keystore

        +

        (Appears on: CertificateKeystores)

        +
        +

        + PKCS12 configures options for storing a PKCS12 keystore in the + spec.secretName Secret resource. +

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + create +
        + bool +
        +

        Create enables PKCS12 keystore creation for the Certificate. If true, a file named keystore.p12 will be created in the target Secret resource, encrypted using the password stored in passwordSecretRef. The keystore file will be updated immediately. If the issuer provided a CA certificate, a file named truststore.p12 will also be created in the target Secret resource, encrypted using the password stored in passwordSecretRef containing the issuing Certificate Authority

        +
        + passwordSecretRef +
        + + SecretKeySelector + +
        +

        PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the PKCS12 keystore.

        +
        +

        PrivateKeyAlgorithm (string alias)

        +

        (Appears on: CertificatePrivateKey)

        +
        + + + + + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "ECDSA"

        +
        +

        ECDSA private key algorithm.

        +
        +

        "Ed25519"

        +
        +

        Ed25519 private key algorithm.

        +
        +

        "RSA"

        +
        +

        RSA private key algorithm.

        +
        +

        PrivateKeyEncoding (string alias)

        +

        (Appears on: CertificatePrivateKey)

        +
        + + + + + + + + + + + + + + + + + +
        ValueDescription
        +

        "PKCS1"

        +
        +

        PKCS1 private key encoding. PKCS1 produces a PEM block that contains the private key algorithm in the header and the private key in the body. A key that uses this can be recognised by its BEGIN RSA PRIVATE KEY or BEGIN EC PRIVATE KEY header. NOTE: This encoding is not supported for Ed25519 keys. Attempting to use this encoding with an Ed25519 key will be ignored and default to PKCS8.

        +
        +

        "PKCS8"

        +
        +

        PKCS8 private key encoding. PKCS8 produces a PEM block with a static header and both the private key algorithm and the private key in the body. A key that uses this encoding can be recognised by its BEGIN PRIVATE KEY header.

        +
        +

        PrivateKeyRotationPolicy (string alias)

        +

        (Appears on: CertificatePrivateKey)

        +
        +

        Denotes how private keys should be generated or sourced when a Certificate is being issued.

        +
        +

        SelfSignedIssuer

        +

        (Appears on: IssuerConfig)

        +
        +

        Configures an issuer to ‘self sign’ certificates using the private key used to create the CertificateRequest object.

        +
        + + + + + + + + + + + + + +
        FieldDescription
        + crlDistributionPoints +
        + []string +
        + (Optional) +

        The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings.

        +
        +

        ServiceAccountRef

        +

        (Appears on: VaultKubernetesAuth)

        +
        +

        ServiceAccountRef is a service account used by cert-manager to request a token. The audience cannot be configured. The audience is generated by cert-manager and takes the form vault://namespace-name/issuer-name for an Issuer and vault://issuer-name for a ClusterIssuer. The expiration of the token is also set by cert-manager to 10 minutes.

        +
        + + + + + + + + + + + + + +
        FieldDescription
        + name +
        + string +
        +

        Name of the ServiceAccount used to request a token.

        +
        +

        VaultAppRole

        +

        (Appears on: VaultAuth)

        +
        +

        VaultAppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + path +
        + string +
        +

        Path where the App Role authentication backend is mounted in Vault, e.g: “approle”

        +
        + roleId +
        + string +
        +

        RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault.

        +
        + secretRef +
        + + SecretKeySelector + +
        +

        Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The key field must be specified and denotes which entry within the Secret resource is used as the app role secret.

        +
        +

        VaultAuth

        +

        (Appears on: VaultIssuer)

        +
        +

        VaultAuth is configuration used to authenticate with a Vault server. The order of precedence is [tokenSecretRef, appRole or kubernetes].

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + tokenSecretRef +
        + + SecretKeySelector + +
        + (Optional) +

        TokenSecretRef authenticates with Vault by presenting a token.

        +
        + appRole +
        + + VaultAppRole + +
        + (Optional) +

        AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource.

        +
        + kubernetes +
        + + VaultKubernetesAuth + +
        + (Optional) +

        Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server.

        +
        +

        VaultIssuer

        +

        (Appears on: IssuerConfig)

        +
        +

        Configures an issuer to sign certificates using a HashiCorp Vault PKI backend.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + auth +
        + + VaultAuth + +
        +

        Auth configures how cert-manager authenticates with the Vault server.

        +
        + server +
        + string +
        +

        Server is the connection address for the Vault server, e.g: “https://vault.example.com:8200”.

        +
        + path +
        + string +
        +

        Path is the mount path of the Vault PKI backend’s sign endpoint, e.g: “my_pki_mount/sign/my-role-name”.

        +
        + namespace +
        + string +
        + (Optional) +

        Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: “ns1” More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces

        +
        + caBundle +
        + []byte +
        + (Optional) +

        Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by Vault. Only used if using HTTPS to connect to Vault and ignored for HTTP connections. Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection.

        +
        + caBundleSecretRef +
        + + SecretKeySelector + +
        + (Optional) +

        Reference to a Secret containing a bundle of PEM-encoded CAs to use when verifying the certificate chain presented by Vault when using HTTPS. Mutually exclusive with CABundle. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. If no key for the Secret is specified, cert-manager will default to ‘ca.crt’.

        +
        +

        VaultKubernetesAuth

        +

        (Appears on: VaultAuth)

        +
        +

        Authenticate against Vault using a Kubernetes ServiceAccount token stored in a Secret.

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + mountPath +
        + string +
        + (Optional) +

        The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to /v1/auth/foo, will use the path /v1/auth/foo/login to authenticate with Vault. If unspecified, the default value “/v1/auth/kubernetes” will be used.

        +
        + secretRef +
        + + SecretKeySelector + +
        + (Optional) +

        The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of ‘ambient credentials’ is not supported.

        +
        + serviceAccountRef +
        + + ServiceAccountRef + +
        + (Optional) +

        A reference to a service account that will be used to request a bound token (also known as “projected token”). Compared to using “secretRef”, using this field means that you don’t rely on statically bound tokens. To use this field, you must configure an RBAC rule to let cert-manager request a token.

        +
        + role +
        + string +
        +

        A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies.

        +
        +

        VenafiCloud

        +

        (Appears on: VenafiIssuer)

        +
        +

        VenafiCloud defines connection configuration details for Venafi Cloud

        +
        + + + + + + + + + + + + + + + + + +
        FieldDescription
        + url +
        + string +
        + (Optional) +

        URL is the base URL for Venafi Cloud. Defaults to “https://api.venafi.cloud/v1”.

        +
        + apiTokenSecretRef +
        + + SecretKeySelector + +
        +

        APITokenSecretRef is a secret key selector for the Venafi Cloud API token.

        +
        +

        VenafiIssuer

        +

        (Appears on: IssuerConfig)

        +
        +

        Configures an issuer to sign certificates using a Venafi TPP or Cloud policy zone.

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + zone +
        + string +
        +

        Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required.

        +
        + tpp +
        + + VenafiTPP + +
        + (Optional) +

        TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified.

        +
        + cloud +
        + + VenafiCloud + +
        + (Optional) +

        Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified.

        +
        +

        VenafiTPP

        +

        (Appears on: VenafiIssuer)

        +
        +

        VenafiTPP defines connection configuration details for a Venafi TPP instance

        +
        + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + url +
        + string +
        +

        URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: “https://tpp.example.com/vedsdk”.

        +
        + credentialsRef +
        + + LocalObjectReference + +
        +

        CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, ‘username’ and ‘password’.

        +
        + caBundle +
        + []byte +
        + (Optional) +

        Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by the TPP server. Only used if using HTTPS; ignored for HTTP. If undefined, the certificate bundle in the cert-manager controller container is used to validate the chain.

        +
        +

        X509Subject

        +

        (Appears on: CertificateSpec)

        +
        +

        X509Subject Full X509 name specification

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        FieldDescription
        + organizations +
        + []string +
        + (Optional) +

        Organizations to be used on the Certificate.

        +
        + countries +
        + []string +
        + (Optional) +

        Countries to be used on the Certificate.

        +
        + organizationalUnits +
        + []string +
        + (Optional) +

        Organizational Units to be used on the Certificate.

        +
        + localities +
        + []string +
        + (Optional) +

        Cities to be used on the Certificate.

        +
        + provinces +
        + []string +
        + (Optional) +

        State/Provinces to be used on the Certificate.

        +
        + streetAddresses +
        + []string +
        + (Optional) +

        Street addresses to be used on the Certificate.

        +
        + postalCodes +
        + []string +
        + (Optional) +

        Postal codes to be used on the Certificate.

        +
        + serialNumber +
        + string +
        + (Optional) +

        Serial number to be used on the Certificate.

        +
        +
        +

        controller.config.cert-manager.io/v1alpha1

        +
        +

        Package v1alpha1 is the v1alpha1 version of the controller config API.

        +
        +

        Resource Types:

        +
          +

          ACMEDNS01Config

          +

          (Appears on: ControllerConfiguration)

          +
          + + + + + + + + + + + + + + + + + + + + + +
          FieldDescription
          + recursiveNameservers +
          + []string +
          +

          Each nameserver can be either the IP address and port of a standard recursive DNS server, or the endpoint to an RFC 8484 DNS over HTTPS endpoint. For example, the following values are valid: - “8.8.8.8:53” (Standard DNS) - “https://1.1.1.1/dns-query” (DNS over HTTPS)

          +
          + recursiveNameserversOnly +
          + bool +
          +

          When true, cert-manager will only ever query the configured DNS resolvers to perform the ACME DNS01 self check. This is useful in DNS constrained environments, where access to authoritative nameservers is restricted. Enabling this option could cause the DNS01 self check to take longer due to caching performed by the recursive nameservers.

          +
          + checkRetryPeriod +
          + time.Duration +
          +

          The duration the controller should wait between a propagation check. Despite the name, this flag is used to configure the wait period for both DNS01 and HTTP01 challenge propagation checks. For DNS01 challenges the propagation check verifies that a TXT record with the challenge token has been created. For HTTP01 challenges the propagation check verifies that the challenge token is served at the challenge URL. This should be a valid duration string, for example 180s or 1h

          +
          +

          ACMEHTTP01Config

          +

          (Appears on: ControllerConfiguration)

          +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          FieldDescription
          + solverImage +
          + string +
          +

          The Docker image to use to solve ACME HTTP01 challenges. You most likely will not need to change this parameter unless you are testing a new feature or developing cert-manager.

          +
          + solverResourceRequestCPU +
          + string +
          +

          Defines the resource request CPU size when spawning new ACME HTTP01 challenge solver pods.

          +
          + solverResourceRequestMemory +
          + string +
          +

          Defines the resource request Memory size when spawning new ACME HTTP01 challenge solver pods.

          +
          + solverResourceLimitsCPU +
          + string +
          +

          Defines the resource limits CPU size when spawning new ACME HTTP01 challenge solver pods.

          +
          + solverResourceLimitsMemory +
          + string +
          +

          Defines the resource limits Memory size when spawning new ACME HTTP01 challenge solver pods.

          +
          + solverRunAsNonRoot +
          + bool +
          +

          Defines the ability to run the http01 solver as root for troubleshooting issues

          +
          + solverNameservers +
          + []string +
          +

          A list of comma separated dns server endpoints used for ACME HTTP01 check requests. This should be a list containing host and port, for example [“8.8.8.8:53”,“8.8.4.4:53”] Allows specifying a list of custom nameservers to perform HTTP01 checks on.

          +
          +

          ControllerConfiguration

          +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          FieldDescription
          + kubeConfig +
          + string +
          +

          kubeConfig is the kubeconfig file used to connect to the Kubernetes apiserver. If not specified, the webhook will attempt to load the in-cluster-config.

          +
          + apiServerHost +
          + string +
          +

          apiServerHost is used to override the API server connection address. Deprecated: use kubeConfig instead.

          +
          + kubernetesAPIQPS +
          + float32 +
          +

          Indicates the maximum queries-per-second requests to the Kubernetes apiserver TODO: floats are not recommended. Maybe we should use resource.Quantity? https://kubernetes.io/docs/reference/kubernetes-api/common-definitions/quantity/

          +
          + kubernetesAPIBurst +
          + int32 +
          +

          The maximum burst queries-per-second of requests sent to the Kubernetes apiserver

          +
          + namespace +
          + string +
          +

          If set, this limits the scope of cert-manager to a single namespace and ClusterIssuers are disabled. If not specified, all namespaces will be watched”

          +
          + clusterResourceNamespace +
          + string +
          +

          Namespace to store resources owned by cluster scoped resources such as ClusterIssuer in.

          +
          + leaderElectionConfig +
          + + LeaderElectionConfig + +
          +

          LeaderElectionConfig configures the behaviour of the leader election

          +
          + controllers +
          + []string +
          +

          A list of controllers to enable. [’’] enables all controllers, [‘foo’] enables only the foo controller [’’, ‘-foo’] disables the controller named foo.

          +
          + issuerAmbientCredentials +
          + bool +
          +

          Whether an issuer may make use of ambient credentials. ‘Ambient Credentials’ are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the Issuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata.

          +
          + clusterIssuerAmbientCredentials +
          + bool +
          +

          Whether a cluster-issuer may make use of ambient credentials for issuers. ‘Ambient Credentials’ are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the ClusterIssuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata.

          +
          + enableCertificateOwnerRef +
          + bool +
          +

          Whether to set the certificate resource as an owner of secret where the tls certificate is stored. When this flag is enabled, the secret will be automatically removed when the certificate resource is deleted.

          +
          + copiedAnnotationPrefixes +
          + []string +
          +

          Specify which annotations should/shouldn’t be copied from Certificate to CertificateRequest and Order, as well as from CertificateSigningRequest to Order, by passing a list of annotation key prefixes. A prefix starting with a dash(-) specifies an annotation that shouldn’t be copied. Example: ‘*,-kubectl.kuberenetes.io/’- all annotations will be copied apart from the ones where the key is prefixed with ‘kubectl.kubernetes.io/’.

          +
          + numberOfConcurrentWorkers +
          + int32 +
          +

          The number of concurrent workers for each controller.

          +
          + maxConcurrentChallenges +
          + int32 +
          +

          The maximum number of challenges that can be scheduled as ‘processing’ at once.

          +
          + metricsListenAddress +
          + string +
          +

          The host and port that the metrics endpoint should listen on.

          +
          + healthzListenAddress +
          + string +
          +

          The host and port address, separated by a ‘:’, that the healthz server should listen on.

          +
          + enablePprof +
          + bool +
          +

          Enable profiling for controller.

          +
          + pprofAddress +
          + string +
          +

          The host and port that Go profiler should listen on, i.e localhost:6060. Ensure that profiler is not exposed on a public address. Profiler will be served at /debug/pprof.

          +
          + logging +
          + k8s.io/component-base/logs/api/v1.LoggingConfiguration +
          +

          + logging configures the logging behaviour of the controller. + https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration +

          +
          + featureGates +
          + map[string]bool +
          + (Optional) +

          featureGates is a map of feature names to bools that enable or disable experimental features. Default: nil

          +
          + ingressShimConfig +
          + + IngressShimConfig + +
          +

          ingressShimConfig configures the behaviour of the ingress-shim controller

          +
          + acmeHTTP01Config +
          + + ACMEHTTP01Config + +
          +

          acmeHTTP01Config configures the behaviour of the ACME HTTP01 challenge solver

          +
          + acmeDNS01Config +
          + + ACMEDNS01Config + +
          +

          acmeDNS01Config configures the behaviour of the ACME DNS01 challenge solver

          +
          +

          IngressShimConfig

          +

          (Appears on: ControllerConfiguration)

          +
          + + + + + + + + + + + + + + + + + + + + + + + + + +
          FieldDescription
          + defaultIssuerName +
          + string +
          +

          Default issuer/certificates details consumed by ingress-shim Name of the Issuer to use when the tls is requested but issuer name is not specified on the ingress resource.

          +
          + defaultIssuerKind +
          + string +
          +

          Kind of the Issuer to use when the TLS is requested but issuer kind is not specified on the ingress resource.

          +
          + defaultIssuerGroup +
          + string +
          +

          Group of the Issuer to use when the TLS is requested but issuer group is not specified on the ingress resource.

          +
          + defaultAutoCertificateAnnotations +
          + []string +
          +

          The annotation consumed by the ingress-shim controller to indicate a ingress is requesting a certificate

          +
          +

          KubeConfig

          +
          + + + + + + + + + + + + + + + + + + + + + +
          FieldDescription
          + path +
          + string +
          +

          Path to a kubeconfig. Only required if out-of-cluster.

          +
          + currentContext +
          + bool +
          + (Optional) +

          If true, use the current context from the kubeconfig file. If false, use the context specified by ControllerConfiguration.Context. Default: true

          +
          + context +
          + string +
          + (Optional) +

          The kubeconfig context to use. Default: current-context from kubeconfig file

          +
          +

          LeaderElectionConfig

          +

          (Appears on: ControllerConfiguration)

          +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          FieldDescription
          + enabled +
          + bool +
          +

          If true, cert-manager will perform leader election between instances to ensure no more than one instance of cert-manager operates at a time

          +
          + namespace +
          + string +
          +

          Namespace used to perform leader election. Only used if leader election is enabled

          +
          + leaseDuration +
          + time.Duration +
          +

          The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled.

          +
          + renewDeadline +
          + time.Duration +
          +

          The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled.

          +
          + retryPeriod +
          + time.Duration +
          +

          The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled.

          +
          + healthzTimeout +
          + time.Duration +
          +

          Leader election healthz checks within this timeout period after the lease expires will still return healthy.

          +
          +
          +

          meta.cert-manager.io/v1

          +
          +

          Package v1 contains meta types for cert-manager APIs

          +
          +

          Resource Types:

          +
            +

            ConditionStatus (string alias)

            +

            (Appears on: CertificateCondition, CertificateRequestCondition, IssuerCondition)

            +
            +

            ConditionStatus represents a condition’s status.

            +
            + + + + + + + + + + + + + + + + + + + + + +
            ValueDescription
            +

            "False"

            +
            +

            ConditionFalse represents the fact that a given condition is false

            +
            +

            "True"

            +
            +

            ConditionTrue represents the fact that a given condition is true

            +
            +

            "Unknown"

            +
            +

            ConditionUnknown represents the fact that a given condition is unknown

            +
            +

            LocalObjectReference

            +

            (Appears on: VenafiTPP, SecretKeySelector)

            +
            +

            A reference to an object in the same namespace as the referent. If the referent is a cluster-scoped resource (e.g. a ClusterIssuer), the reference instead refers to the resource with the given name in the configured ‘cluster resource namespace’, which is set as a flag on the controller component (and defaults to the namespace that cert-manager runs in).

            +
            + + + + + + + + + + + + + +
            FieldDescription
            + name +
            + string +
            +

            Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

            +
            +

            ObjectReference

            +

            (Appears on: ChallengeSpec, OrderSpec, CertificateRequestSpec, CertificateSpec)

            +
            +

            ObjectReference is a reference to an object with a given name, kind and group.

            +
            + + + + + + + + + + + + + + + + + + + + + +
            FieldDescription
            + name +
            + string +
            +

            Name of the resource being referred to.

            +
            + kind +
            + string +
            + (Optional) +

            Kind of the resource being referred to.

            +
            + group +
            + string +
            + (Optional) +

            Group of the resource being referred to.

            +
            +

            SecretKeySelector

            +

            + (Appears on: ACMEExternalAccountBinding, ACMEIssuer, ACMEIssuerDNS01ProviderAcmeDNS, ACMEIssuerDNS01ProviderAkamai, ACMEIssuerDNS01ProviderAzureDNS, ACMEIssuerDNS01ProviderCloudDNS, ACMEIssuerDNS01ProviderCloudflare, ACMEIssuerDNS01ProviderDigitalOcean, ACMEIssuerDNS01ProviderRFC2136, + ACMEIssuerDNS01ProviderRoute53, JKSKeystore, PKCS12Keystore, VaultAppRole, VaultAuth, VaultIssuer, VaultKubernetesAuth, VenafiCloud) +

            +
            +

            A reference to a specific ‘key’ within a Secret resource. In some instances, key is a required field.

            +
            + + + + + + + + + + + + + + + + + +
            FieldDescription
            + LocalObjectReference +
            + + LocalObjectReference + +
            +

            (Members of LocalObjectReference are embedded into this type.)

            +

            The name of the Secret resource being referred to.

            +
            + key +
            + string +
            + (Optional) +

            The key of the entry in the Secret resource’s data field to be used. Some instances of this field may be defaulted, in others it may be required.

            +
            +
            +

            webhook.config.cert-manager.io/v1alpha1

            +
            +

            Package v1alpha1 is the v1alpha1 version of the webhook config API.

            +
            +

            Resource Types:

            +
              +

              DynamicServingConfig

              +

              (Appears on: TLSConfig)

              +
              +

              DynamicServingConfig makes the webhook generate a CA and persist it into Secret resources. This CA will be used by all instances of the webhook for signing serving certificates.

              +
              + + + + + + + + + + + + + + + + + + + + + +
              FieldDescription
              + secretNamespace +
              + string +
              +

              Namespace of the Kubernetes Secret resource containing the TLS certificate used as a CA to sign dynamic serving certificates.

              +
              + secretName +
              + string +
              +

              Namespace of the Kubernetes Secret resource containing the TLS certificate used as a CA to sign dynamic serving certificates.

              +
              + dnsNames +
              + []string +
              +

              DNSNames that must be present on serving certificates signed by the CA.

              +
              +

              FilesystemServingConfig

              +

              (Appears on: TLSConfig)

              +
              +

              FilesystemServingConfig enables using a certificate and private key found on the local filesystem. These files will be periodically polled in case they have changed, and dynamically reloaded.

              +
              + + + + + + + + + + + + + + + + + +
              FieldDescription
              + certFile +
              + string +
              +

              Path to a file containing TLS certificate & chain to serve with

              +
              + keyFile +
              + string +
              +

              Path to a file containing a TLS private key to server with

              +
              +

              TLSConfig

              +

              (Appears on: WebhookConfiguration)

              +
              +

              TLSConfig configures how TLS certificates are sourced for serving. Only one of ‘filesystem’ or ‘dynamic’ may be specified.

              +
              + + + + + + + + + + + + + + + + + + + + + + + + + +
              FieldDescription
              + cipherSuites +
              + []string +
              +

              cipherSuites is the list of allowed cipher suites for the server. Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). If not specified, the default for the Go version will be used and may change over time.

              +
              + minTLSVersion +
              + string +
              +

              minTLSVersion is the minimum TLS version supported. Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). If not specified, the default for the Go version will be used and may change over time.

              +
              + filesystem +
              + + FilesystemServingConfig + +
              +

              Filesystem enables using a certificate and private key found on the local filesystem. These files will be periodically polled in case they have changed, and dynamically reloaded.

              +
              + dynamic +
              + + DynamicServingConfig + +
              +

              When Dynamic serving is enabled, the webhook will generate a CA used to sign webhook certificates and persist it into a Kubernetes Secret resource (for other replicas of the webhook to consume). It will then generate a certificate in-memory for itself using this CA to serve with. The CAs certificate can then be copied into the appropriate Validating, Mutating and Conversion webhook configuration objects (typically by cainjector).

              +
              +

              WebhookConfiguration

              +
              + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
              FieldDescription
              + securePort +
              + int32 +
              +

              securePort is the port number to listen on for secure TLS connections from the kube-apiserver. If 0, a random available port will be chosen. Defaults to 6443.

              +
              + healthzPort +
              + int32 +
              +

              healthzPort is the port number to listen on (using plaintext HTTP) for healthz connections. If 0, a random available port will be chosen. Defaults to 6080.

              +
              + tlsConfig +
              + + TLSConfig + +
              +

              tlsConfig is used to configure the secure listener’s TLS settings.

              +
              + kubeConfig +
              + string +
              +

              kubeConfig is the kubeconfig file used to connect to the Kubernetes apiserver. If not specified, the webhook will attempt to load the in-cluster-config.

              +
              + apiServerHost +
              + string +
              +

              apiServerHost is used to override the API server connection address. Deprecated: use kubeConfig instead.

              +
              + enablePprof +
              + bool +
              +

              enablePprof configures whether pprof is enabled.

              +
              + pprofAddress +
              + string +
              +

              pprofAddress configures the address on which /debug/pprof endpoint will be served if enabled. Defaults to ‘localhost:6060’.

              +
              + logging +
              + k8s.io/component-base/logs/api/v1.LoggingConfiguration +
              +

              + logging configures the logging behaviour of the webhook. + https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration +

              +
              + featureGates +
              + map[string]bool +
              + (Optional) +

              featureGates is a map of feature names to bools that enable or disable experimental features. Default: nil

              +
              +
              +

              + Generated with gen-crd-api-reference-docs on git commit d34bd7a. +

              diff --git a/content/v1.13-docs/reference/cmctl.md b/content/v1.13-docs/reference/cmctl.md new file mode 100644 index 0000000000..095c9dfe6e --- /dev/null +++ b/content/v1.13-docs/reference/cmctl.md @@ -0,0 +1,344 @@ +--- +title: The cert-manager Command Line Tool (cmctl) +description: | + cmctl is a command line tool that can help you manage cert-manager and its resources inside your cluster +--- + +`cmctl` is a command line tool that can help you manage cert-manager and its resources inside your cluster. + +## Installation + +### Homebrew + +On Mac or Linux if you have [Homebrew](https://brew.sh) installed, you can +install `cmctl` with: + +```console +brew install cmctl +``` + +This will also install shell completion. + +### Manual Installation + +You need the `cmctl.tar.gz` file for the platform you're using, these can be +found on our +[GitHub releases page](https://github.com/cert-manager/cert-manager/releases). +In order to use `cmctl` you need its binary to be accessible under +the name `cmctl` in your `$PATH`. +Run the following commands to set up the CLI. Replace OS and ARCH with your +systems equivalents: + +```console +OS=$(go env GOOS); ARCH=$(go env GOARCH); curl -fsSL -o cmctl.tar.gz https://github.com/cert-manager/cert-manager/releases/latest/download/cmctl-$OS-$ARCH.tar.gz +tar xzf cmctl.tar.gz +sudo mv cmctl /usr/local/bin +``` + +You can run `cmctl help` to test the CLI is set up properly: + +```console +$ cmctl help + +cmctl is a CLI tool manage and configure cert-manager resources for Kubernetes + +Usage: cmctl [command] + +Available Commands: + approve Approve a CertificateRequest + check Check cert-manager components + completion Generate completion scripts for the cert-manager CLI + convert Convert cert-manager config files between different API versions + create Create cert-manager resources + deny Deny a CertificateRequest + experimental Interact with experimental features + help Help about any command + inspect Get details on certificate related resources + renew Mark a Certificate for manual renewal + status Get details on current status of cert-manager resources + upgrade Tools that assist in upgrading cert-manager + version Print the cert-manager CLI version and the deployed cert-manager version + +Flags: + -h, --help help for cmctl + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + +Use "cmctl [command] --help" for more information about a command. +``` + +> There is also a [legacy kubectl plugin](#legacy-kubectl-plugin), but it is no longer recommended +> because the standalone `cmctl` binary provides better [auto-completion](#completion). + +## Commands + +### Approve and Deny CertificateRequests + +CertificateRequests can be +[approved or denied](../concepts/certificaterequest.md#approval) using their +respective cmctl commands: + +> **Note**: The internal cert-manager approver may automatically approve all +> CertificateRequests unless disabled with the flag on the cert-manager-controller +> `--controllers=*,-certificaterequests-approver` + +```bash +$ cmctl approve -n istio-system mesh-ca --reason "pki-team" --message "this certificate is valid" +Approved CertificateRequest 'istio-system/mesh-ca' +``` + +```bash +$ cmctl deny -n my-app my-app --reason "example.com" --message "violates policy" +Denied CertificateRequest 'my-app/my-app' +``` + +### Convert + +`cmctl convert` can be used to convert cert-manager manifest files between +different API versions. Both YAML and JSON formats are accepted. The command +either takes a file name, directory path, or a URL as input. The contents is +converted into the format of the latest API version known to cert-manager, or +the one specified by `--output-version` flag. + +The default output will be printed to stdout in YAML format. One can use the +option `-o` to change the output destination. + +For example, this will output `cert.yaml` in the latest API version: + +```console +cmctl convert -f cert.yaml +``` + +### Create + +`cmctl create` can be used to create cert-manager resources manually. +Sub-commands are available to create different resources: + +#### CertificateRequest + +To create a cert-manager CertificateRequest, use `cmctl create +certificaterequest`. The command takes in the name of the CertificateRequest to +be created, and creates a new CertificateRequest resource based on the YAML +manifest of a Certificate resource as specified by `--from-certificate-file` +flag, by generating a private key locally and creating a 'certificate signing +request' to be submitted to a cert-manager Issuer. The private key will be +written to a local file, where the default is `.key`, or it can be +specified using the `--output-key-file` flag. + +If you wish to wait for the CertificateRequest to be signed and store the X.509 +certificate in a file, you can set the `--fetch-certificate` flag. The default +timeout when waiting for the issuance of the certificate is 5 minutes, but can +be specified with the `--timeout` flag. The default name of the file storing the +X.509 certificate is `.crt`, you can use the ` +--output-certificate-file` flag to specify otherwise. + +Note that the private key and the X.509 certificate are both written to file, +and are **not** stored inside Kubernetes. + +For example this will create a CertificateRequest resource with the name "my-cr" +based on the cert-manager Certificate described in `my-certificate.yaml` while +storing the private key and X.509 certificate in `my-cr.key` and `my-cr.crt` +respectively. + +```console +cmctl create certificaterequest my-cr --from-certificate-file my-certificate.yaml --fetch-certificate --timeout 20m +``` + +### Renew + +`cmctl` allows you to manually trigger a renewal of a specific certificate. +This can be done either one certificate at a time, using label selectors (`-l app=example`), or with the `--all` flag: + +For example, you can renew the certificate `example-com-tls`: +```console +$ kubectl get certificate +NAME READY SECRET AGE +example-com-tls True example-com-tls 1d + +$ cmctl renew example-com-tls +Manually triggered issuance of Certificate default/example-com-tls + +$ kubectl get certificaterequest +NAME READY AGE +example-com-tls-tls-8rbv2 False 10s +``` + +You can also renew all certificates in a given namespace: + +```console +$ cmctl renew --namespace=app --all +``` + +The renew command allows several options to be specified: +* `--all` renew all Certificates in the given Namespace, or all namespaces when combined with `--all-namespaces` +* `-A` or `--all-namespaces` mark Certificates across namespaces for renewal +* `-l` `--selector` allows set a label query to filter on +as well as `kubectl` like global flags like `--context` and `--namespace`. + +### Status Certificate + +`cmctl status certificate` outputs the details of the current status of a +Certificate resource and related resources like CertificateRequest, Secret, +Issuer, as well as Order and Challenges if it is a ACME Certificate. The +command outputs information about the resources, including Conditions, Events +and resource specific fields like Key Usages and Extended Key Usages of the +Secret or Authorizations of the Order. This will be helpful for troubleshooting +a Certificate. + +The command takes in one argument specifying the name of the Certificate +resource and the namespace can be specified as usual with the `-n` or +`--namespace` flag. + +This example queries the status of the Certificate named `my-certificate` in +namespace `my-namespace`. + +```console +cmctl status certificate my-certificate -n my-namespace +``` + +### Completion + +`cmctl` supports auto-completion for both subcommands as well as suggestions for +runtime objects. + +```console +$ cmctl approve -n +default kube-node-lease kube-public kube-system local-path-storage +``` + +Completion can be installed for your environment by following the instructions +for the shell you are using. It currently supports bash, fish, zsh, and +powershell. + +```console +$ cmctl completion help +``` + +--- + +### Experimental +`cmctl x` has experimental sub-commands for operations which are currently under +evaluation to be included into cert-manager proper. The behavior and interface +of these commands are subject to change or removal in future releases. + + +#### Create +`cmctl x create` can be used to create cert-manager resources manually. +Sub-commands are available to create different resources: + +##### CertificateSigningRequest +To create a [CertificateSigningRequest](../usage/kube-csr.md), use +```console +cmctl x create csr +``` +This command takes the name of the CertificateSigningRequest to be created, as +well as a file containing a Certificate manifest (`-f, +--from-certificate-file`). This command will generate a private key, based on +the options of the Certificate, and write it to the local file `.key`, or +specified by `-k, --output-key-file`. + +```bash +$ cmctl x create csr -f my-cert.yaml my-req +``` + + +
              + +cert-manager **will not** automatically approve CertificateSigningRequests. If +you are not running a custom approver in your cluster, you will likely need to +manually approve the CertificateSigningRequest: + +```bash +$ kubectl certificate approve +``` + +
              + +This command can also wait for the CertificateSigningRequest to be signed using +the flag `-w, --fetch-certificate`. Once signed it will write the resulting +signed certificate to the local file `.crt`, or specified by `-c, +--output-certificate-file`. + +```bash +$ cmctl x create csr -f my-cert.yaml my-req -w +``` + +#### Install + +```bash +cmctl x install +``` + +This command makes sure that the required `CustomResourceDefinitions` are installed together with the cert-manager, cainjector and webhook components. +Under the hood, a procedure similar to the [Helm install procedure](../installation/helm.md#steps) is used. + +You can also use `cmctl x install` to customize the installation of cert-manager. + +The example below shows how to tune the cert-manager installation by overriding the default Helm values: + +```bash +cmctl x install \ + --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter + --set webhook.timeoutSeconds=4s # Example: changing the wehbook timeout using a Helm parameter +``` + +You can find [a full list of the install parameters on cert-manager's ArtifactHub page](https://artifacthub.io/packages/helm/cert-manager/cert-manager#configuration). These are the same parameters that are available when using the Helm chart. +Once you have deployed cert-manager, you can [verify](../installation/verify.md) the installation. + +The CLI also allows the user to output the templated manifest to `stdout`, instead of installing the manifest on the cluster. + +```bash +cmctl x install --dry-run > cert-manager.custom.yaml +``` + +#### Uninstall + +```bash +cmctl x uninstall +``` + +This command uninstalls any Helm-managed release of cert-manager. + +The CRDs will be deleted if you installed cert-manager with the option `--set CRDs=true`. + +Most of the features supported by `helm uninstall` are also supported by this command. + +Some example uses: + +```bash +cmctl x uninstall + +cmctl x uninstall --namespace my-cert-manager + +cmctl x uninstall --dry-run + +cmctl x uninstall --no-hooks +``` + +### Upgrade + +Tools that assist in upgrading cert-manager + +```bash +$ cmctl upgrade --help +``` +##### Migrate API version + +This command can be used to prepare a cert-manager installation that was created +before cert-manager `v1` for upgrading to a cert-manager version `v1.6` or later. +It ensures that any cert-manager custom resources that may have been stored in etcd at +a deprecated API version get migrated to `v1`. See [Migrating Deprecated API +Resources](https://cert-manager.io/docs/installation/upgrading/remove-deprecated-apis) for more context. + +```bash +$ cmctl upgrade migrate-api-version --qps 5 --burst 10 +``` + +## Legacy kubectl plugin + +While the kubectl plugin is supported, it is recommended to use `cmctl` as this enables a better experience via tab auto-completion. + +To install the plugin you need the `kubectl-cert-manager.tar.gz` file for the platform you're using, +these can be found on our [GitHub releases page](https://github.com/cert-manager/cert-manager/releases). +In order to use the kubectl plugin you need its binary to be accessible under the name `kubectl-cert_manager` in your `$PATH`. + +You can run `kubectl cert-manager help` to test that the plugin is set up properly. diff --git a/content/v1.13-docs/reference/tls-terminology.md b/content/v1.13-docs/reference/tls-terminology.md new file mode 100644 index 0000000000..23b6df3447 --- /dev/null +++ b/content/v1.13-docs/reference/tls-terminology.md @@ -0,0 +1,79 @@ +--- +title: TLS Terminology +description: | + Learn about the TLS terminology used in the cert-manager documentation such as publicly trusted, self-signed, root, intermediate and leaf certificate +--- + +Learn about the TLS terminology used in the cert-manager documentation such as `publicly trusted`, `self-signed`, `root`, `intermediate` and `leaf` _certificate_. + +## Overview + +With TLS being so widely deployed, terminology can sometimes get confused or be used to mean different things, and that reality +combined with the complexity of TLS can lead to serious misunderstandings and confusion. + +For further reference, you might want to check out some relevant RFCs: + +- [RFC 5246: TLS 1.2](https://datatracker.ietf.org/doc/html/rfc5246) +- [RFC 8446: TLS 1.3](https://datatracker.ietf.org/doc/html/rfc8446) +- [RFC 5280: X.509](https://datatracker.ietf.org/doc/html/rfc5280) + +## Definitions + +### `publicly trusted` + +What does "publicly trusted" mean? + +Broadly speaking, a "publicly trusted" certificate is one that you can use on the Internet and expect +that most reasonably up-to-date computers will be able to verify it using their system trust store. + +There isn't a single standard trust store containing certs which are "publicly trusted", but generally most +of the commonly seen trust stores are similar. An example would be [Mozilla's CA Certificate Program](https://wiki.mozilla.org/CA). + +### What does "self-signed" mean? Is my CA self-signed? + +Self-signed means exactly what it says; a certificate is self-signed if it is signed by its own private key. + +Self-signed is a commonly confused term, however, and is very frequently misused to mean "not publicly trusted". We tend to use terms +like "private PKI" to denote the situation where an organization might have their own internal CA certificates which wouldn't +be trusted outside of the organization. + +As an example, there are _many_ self-signed certificates in [Mozilla's CA Certificate Program](https://wiki.mozilla.org/CA), but +all of those certificates would usually be described as "publicly trusted". + +Your certificate is self-signed only if it's signed with its own key. + +### What's the difference between "root", "intermediate", and "leaf" certificates? + +cert-manager uses the following definitions: + +#### Root Certificates + +Roots are self-signed certificates and almost always marked as CA certificates. They're usually not sent over the wire +during a TLS handshake because they need to be explicitly trusted in order to be validated. + +Roots are sometimes defined as "CA certificates which are explicitly trusted"---which can include certificates which +aren't self-signed. cert-manager doesn't use this definition. + +Changing trust stores to include new roots or remove old ones is a non-trivial task which can take months or years for publicly +trusted roots. For this reason roots are usually issued with very long lifetimes, often on the order of decades. + +#### Intermediate Certificates + +Intermediates are CA certificates signed by another CA. Most intermediates will be signed by a root certificate, but it's +possible to construct longer chains where an intermediate can be signed by another intermediate. + +Intermediate certificates are usually issued with a much shorter lifetime than the CA which signed them. On the +Internet, intermediate certificates are used on network-connected machines for day-to-day issuance so that the +highly-valuable root certificates can remain entirely offline. + +While intermediate certificates can also be explicitly trusted via addition to a trust store, they're usually validated +by "walking up" the chain and validating signatures until an explicitly trusted self-signed root certificate is found. + +#### Leaf Certificates + +Leaf certificates are usually used to represent a particular identity, rather than being used to sign other certificates. +On the Internet leaf certificates usually identify a particular domain, such as `example.com`. + +Leaf certificates are sent first in a chain of certificates and represent the end of that chain. They must be sent +along with any intermediates required to create a chain which can be validated by verifying signatures up to a trusted +root certificate. diff --git a/content/v1.13-docs/troubleshooting/README.md b/content/v1.13-docs/troubleshooting/README.md new file mode 100644 index 0000000000..fff96f1672 --- /dev/null +++ b/content/v1.13-docs/troubleshooting/README.md @@ -0,0 +1,116 @@ +--- +title: Troubleshooting +description: | + Learn how to debug common problems with cert-manager +--- + +In this section, you will learn troubleshooting techniques that will help you find the root cause if your Certificate fails to be issued or renewed. + +This section also includes the following guides: + +* [Troubleshooting Problems with ACME / Let's Encrypt Certificates](./acme.md): + Learn more about how the ACME issuer works and how to diagnose problems with it. +* [Troubleshooting Problems with the Webhook](./webhook.md): + Learn how to diagnose problems with the cert-manager webhook. + +## Overview + +When troubleshooting cert-manager your best friend is `kubectl describe`, this will give you information on the resources as well as recent events. It is not advised to use the logs as these are quite verbose and only should be looked at if the following steps do not provide help. + +cert-manager consists of multiple custom resources that live inside your Kubernetes cluster, these resources are linked together and are often created by one another. When such an event happens it will be reflected in a Kubernetes event, you can see these per-namespace using `kubectl get event`, or in the output of `kubectl describe` when looking at a single resource. + +## Troubleshooting a failed certificate request + +There are several resources that are involved in requesting a certificate. + +``` + + ( +---------+ ) + ( | Ingress | ) Optional ACME Only! + ( +---------+ ) + | | + | +-------------+ +--------------------+ | +-------+ +-----------+ + |-> | Certificate |----> | CertificateRequest | ----> | | Order | ----> | Challenge | + +-------------+ +--------------------+ | +-------+ +-----------+ + | +``` + +The cert-manager flow all starts at a `Certificate` resource, you can create this yourself or your Ingress resource will do this for you if you have the [correct annotations](../usage/ingress.md) set. + +### 1. Checking the Certificate resource +First we have to check if we have a `Certificate` resource created in our namespace. We can get these using `kubectl get certificate`. +```console +$ kubectl get certificate +NAME READY AGE +example-com-tls False 1h +``` + +If none is present and you plan to use the [ingress-shim](../usage/ingress.md): check the ingress annotations more about that is in the [ingress troubleshooting guide](../usage/ingress.md#troubleshooting). +If you are not using the ingress-shim: check the output of the command you used to create the certificate. + +If you see one with ready status `False` you can get more info using `kubectl describe certificate`, if the status is `True` that means that cert-manager has successfully issued a certificate. +```console +$ kubectl describe certificate +[...] +Status: + Conditions: + Last Transition Time: 2020-05-15T21:45:22Z + Message: Issuing certificate as Secret does not exist + Reason: DoesNotExist + Status: False + Type: Ready + Next Private Key Secret Name: example-tls-wtlww +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Issuing 105s cert-manager Issuing certificate as Secret does not exist + Normal Generated 105s cert-manager Stored new private key in temporary Secret resource "example-tls-wtlww" + Normal Requested 104s cert-manager Created new CertificateRequest resource "example-tls-bw5t9" +``` + +Here you will find more info about the current certificate status under `Status` as well as detailed information about what happened to it under `Events`. Both will help you determine the current state of the certificate. +The last status is `Created new CertificateRequest resource`, it is worth taking a look at in which state `CertificateRequest` is to get more info on why our `Certificate` isn't getting issued. + +### 2. Checking the `CertificateRequest` +The `CertificateRequest` resource represents a CSR in cert-manager and passes this CSR on onto the issuer. +You can find the name of the `CertificateRequest` in the `Certificate` event log or using `kubectl get certificaterequest` + +To get more info we again run `kubectl describe`: +```console +$ kubectl describe certificaterequest +API Version: cert-manager.io/v1 +Kind: CertificateRequest +Spec: + Request: [...] + Issuer Ref: + Group: cert-manager.io + Kind: ClusterIssuer + Name: letencrypt-production +Status: + Conditions: + Last Transition Time: 2020-05-15T21:45:36Z + Message: Waiting on certificate issuance from order example-tls-fqtfg-1165244518: "pending" + Reason: Pending + Status: False + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal OrderCreated 8m20s cert-manager Created Order resource example-tls-fqtfg-1165244518 +``` + +Here we will see any issue regarding the Issuer configuration as well as Issuer responses. + +### 3. Check the issuer state +If in the above steps you saw an issuer not ready error you can do the same steps again for (cluster)issuer resources: +```console +$ kubectl describe issuer +$ kubectl describe clusterissuer +``` + +These will allow you to get any error messages regarding accounts or network issues with your issuer. +Troubleshooting ACME issuers is described in more detail in [Troubleshooting Issuing ACME Certificates](./acme.md). + +### 4. ACME Troubleshooting +ACME (e.g. Let's Encrypt) issuers have 2 additional resources inside cert-manager: `Orders` and `Challenges`. +Troubleshooting these is described in [Troubleshooting Issuing ACME Certificates](./acme.md). diff --git a/content/v1.13-docs/troubleshooting/acme.md b/content/v1.13-docs/troubleshooting/acme.md new file mode 100644 index 0000000000..c8b527b1cf --- /dev/null +++ b/content/v1.13-docs/troubleshooting/acme.md @@ -0,0 +1,226 @@ +--- +title: Troubleshooting Problems with ACME / Let's Encrypt Certificates +description: | + Learn how to diagnose problems if cert-manager fails to renew ACME / Let's Encrypt Certificates. +--- + +Learn how to diagnose problems if cert-manager fails to renew ACME / Let's Encrypt Certificates. + +## Overview + +When requesting ACME certificates, cert-manager will create `Order` and +`Challenges` to complete the request. As such, there are more resources to +investigate and debug if there is a problem during the process. You can read +more about these resources in the [concepts +pages](../concepts/acme-orders-challenges.md). + +Before you start here you should probably take a look at our [general troubleshooting guide](./README.md) + +## 1. Troubleshooting (Cluster)Issuers + +First of all check if the (Cluster)Issuer you're using is in a ready state: +```bash +$ kubectl get issuer +$ kubectl get clusterissuer +NAME READY AGE +letsencrypt True 38m +letsencrypt-http False 32m +``` + +If you see `False` check the status using `kubectl describe`. For example: +```bash +$ kubectl describe issuer letsencrypt-http +$ kubectl describe clusterissuer letsencrypt-http +Name: letsencrypt +API Version: cert-manager.io/v1 +Kind: Issuer +Spec: + Acme: + Email: cert-manager@example.com + Private Key Secret Ref: + Name: letsencrypt + Server: https://acme-staging-v02.api.letsencrypt.org/directory +Status: + Acme: + Conditions: + Message: Failed to update ACME account:400 urn:ietf:params:acme:error:invalidEmail: Unable to update account :: invalid contact domain. Contact emails @example.com are forbidden + Reason: ErrUpdateACMEAccount + Status: False + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Warning ErrUpdateACMEAccount 101s (x3 over 106s) cert-manager Failed to update ACME account:400 urn:ietf:params:acme:error:invalidEmail: Unable to update account :: invalid contact domain. Contact emails @example.com are forbidden +``` + +### Common errors + +* `Failed to update ACME account:400 urn:ietf:params:acme:error:invalidEmail`: the email you specified in the Issuer configuration isn't valid. +* `Error initializing issuer: Failed to register ACME account: secrets "acme-key" already exists`: there might be a leftover account from a previous issuer that is no longer valid, you should remove the secret so it can be recreated. +* `Error accepting challenge: 400 urn:ietf:params:acme:error:malformed: Unable to update challenge :: authorization must be pending`: this suggests that the authorization was not in 'pending' state at a time when cert-manager sent a request to the ACME server to accept the challenge. This may be because the domain validation has already failed and the authorization has been marked as 'invalid'. Check the authorization URL on the status of the `Order` or `Challenge` to see the status of the authorization and any additional information. + +## 2. Troubleshooting Orders + +When we run a describe on the `CertificateRequest` resource we see that an `Order` that has +been created: + +```bash +$ kubectl describe certificaterequest example-com-2745722290 +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal OrderCreated 5s cert-manager Created Order resource default/example-com-2745722290-439160286 +``` + +Orders are a request to an ACME instance to issue a certificate. +By running `kubectl describe order` on a particular order, +information can be gleaned about failures in the process: + +```console +$ kubectl describe order example-com-2745722290-439160286 +... +Reason: +State: pending +URL: https://acme-v02.api.letsencrypt.org/acme/order/41123272/265506123 +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Created 1m cert-manager Created Challenge resource "example-com-2745722290-439160286-0" for domain "test1.example.com" + Normal Created 1m cert-manager Created Challenge resource "example-com-2745722290-439160286-1" for domain "test2.example.com" +``` + +Here we can see that cert-manager has created two Challenge resources to verify we control specific domains, +a requirements of the ACME order to obtain a signed certificate. + +You can then go on to run +`kubectl describe challenge example-com-2745722290-439160286-0` to further debug the +progress of the Order. + +Once an Order is successful, you should see an event like the following: + +```bash +$ kubectl describe order example-com-2745722290-439160286 +... +Reason: +State: valid +URL: https://acme-v02.api.letsencrypt.org/acme/order/41123272/265506123 +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Created 72s cert-manager Created Challenge resource "example-com-2745722290-439160286-0" for domain "test1.example.com" + Normal Created 72s cert-manager Created Challenge resource "example-com-2745722290-439160286-1" for domain "test2.example.com" + Normal OrderValid 4s cert-manager Order completed successfully +``` + +You can see some additional information about the state of the [ACME authorization](https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.4) that needs to be validated as part of this order using the authorization URL from the status of the `Order`: + +```bash +$ kubectl get order -ojsonpath='{.status.authorizations[x].url}' +``` + +If the Order is not completing successfully, you can debug the challenges +for the Order by running `kubectl describe` on the `Challenge` resource which is described in the following steps. + +## 3. Troubleshooting Challenges + +In order to determine why an ACME Order is not being finished, we can debug +using the `Challenge` resources that cert-manager has created. + +In order to determine which `Challenge` is failing, you can run +`kubectl get challenges`: + + +```console +$ kubectl get challenges +... +NAME STATE DOMAIN REASON AGE +example-com-2745722290-4391602865-0 pending example.com Waiting for dns-01 challenge propagation 22s +``` + +This shows that the challenge has been presented using the DNS01 solver +successfully and now cert-manager is waiting for the 'self check' to pass. + +You can get more information about the challenge and it's lifecycle by using `kubectl describe`: + +```bash +$ kubectl describe challenge example-com-2745722290-4391602865-0 +... +Status: + Presented: true + Processing: true + Reason: Waiting for dns-01 challenge propagation + State: pending +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Started 19s cert-manager Challenge scheduled for processing + Normal Presented 16s cert-manager Presented challenge using dns-01 challenge mechanism +``` + +Progress about the state of each challenge will be recorded either as Events +or on the Challenge's `status` block (as shown above). + +In case of DNS01 you will find any errors from your DNS provider here. + +Both HTTP01 and DNS01 go through a "self-check" first before cert-manager presents the challenge to the ACME provider. +This is done not to overload the ACME provider with failed challenges due to DNS or loadbalancer propagations. +The status of this can be found in the Status block of the describe: +```console +$ kubectl describe challenge +[...] +Status: + Presented: true + Processing: true + Reason: Waiting for http-01 challenge propagation: failed to perform self check GET request 'http://example.com/.well-known/acme-challenge/_fgdLz0i3TFiZW4LBjuhjgd5nTOkaMBhxYmTY': Get "http://example.com/.well-known/acme-challenge/_fgdLz0i3TFiZW4LBjuhjgd5nTOkaMBhxYmTY: remote error: tls: handshake failure + State: pending +[...] +``` + +In this example our HTTP01 check fails due a network issue. +You will also see any errors coming from your DNS provider here. + +You can also see some additional information about the state of the [ACME authorization](https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.4) that the challenge should validate using the authorization URL on from the status of the `Challenge`: + +```bash +$ kubectl get challenge -ojsonpath='{.spec.authorizationURL}' +``` + +### HTTP01 troubleshooting +First of all check if you can see the challenge URL from the public internet, if this does not work check your Ingress and firewall configuration as well as the service and pod cert-manager created to solve the ACME challenge. +If this does work check if your cluster can see it too. It is important to test this from inside a Pod. If you get a connection error it is suggested to check the cluster's network configuration. +If you receive a `tls: handshake failure`, try setting the annotation `cert-manager.io/issue-temporary-certificate: "true"` on the Ingress or Certificate resource. This will issue a temporary self signed certificate for the ingress controller to use before the actual certificate is issued. +If you still are having issues, there may be an issue with your ingress controller handling multiple resources for the same hostname, in this case, the annotation `acme.cert-manager.io/http01-edit-in-place: "true"` is likely required. + +For example when using GKE with the Google Cloud Loadbalancer it is recommended to set: +``` +cert-manager.io/issue-temporary-certificate: "true" +acme.cert-manager.io/http01-edit-in-place: "true" +``` +This will allow the Google Cloud Loadbalancer to propagate a HTTPS endpoint correctly with a temporary certificate, the `http01-edit-in-place` part will prevent GKE from assigning a 2nd IP address for the challenge endpoint. + +#### Got 404 status code +If your challenge self-check fails with a 404 not found error. Make sure to check the following: + +* you can access the URL from the public internet +* the ACME solver pod is up and running +* use `kubectl describe ingress` to check the status of the HTTP01 solver ingress. (unless you use `acme.cert-manager.io/http01-edit-in-place`, then check the same ingress as your domain) + +### DNS01 troubleshooting +If you see no error events about your DNS provider you can check the following +Check if you can see the `_acme_challenge.domain` TXT DNS record from the public internet, or in your DNS provider's interface. +cert-manager will check if a DNS record has been propagated by querying the cluster's DNS solver. If you are able to see it from the public internet but not from inside the cluster you might want to change [the DNS server for self-check](../configuration/acme/dns01/README.md#setting-nameservers-for-dns01-self-check) as some cloud providers overwrite DNS internally. + +#### cert-manager identifies the wrong zone for your domain name +cert-manager by default uses SOA (Start of Authority) records to determine which zone name to use at your DNS provider. +Some DNS resolvers will filter this information, if this is the case cert-manager cannot determine the zone and it is advised to [change the DNS server for DNS01 self-checks](../configuration/acme/dns01/README.md#setting-nameservers-for-dns01-self-check). + +If you use `dnsmasq` as your DNS server, this may occur if you use the [`--filterwin2k` flag](http://www.thekelleys.org.uk/dnsmasq/docs/setup.html). +In [OpenWRT there is a `filterwin2k` configuration option](https://openwrt.org/docs/guide-user/base-system/dhcp#all_options). +And in [LuCI there is a "Filter useless" option](https://github.com/openwrt/luci/blob/15757dd5b18f9e00ba3c9b38af4d46702a31fe33/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js#L217-L219). +By enabling this flag, `dnsmasq` drops all `SOA` records. + +## March 2020 Let's Encrypt CAA Rechecking Bug +Following the [announcement on March 4](https://community.letsencrypt.org/t/revoking-certain-certificates-on-march-4/114864) Let's Encrypt will be revoking a number of certificates due to a bug in the way they validate CAA records, we have created a tool to analyse your existing cert-manager managed certificates and compare their serial numbers to the publicised list of revoked certificates. +It's advised that all users of Let's Encrypt & cert-manager run a check using this tool to ensure they do not experience any invalid certificate errors in clusters. +You can find a copy of the checker tool here: https://github.com/jetstack/letsencrypt-caa-bug-checker. diff --git a/content/v1.13-docs/troubleshooting/webhook.md b/content/v1.13-docs/troubleshooting/webhook.md new file mode 100644 index 0000000000..b749a9d40f --- /dev/null +++ b/content/v1.13-docs/troubleshooting/webhook.md @@ -0,0 +1,1068 @@ +--- +title: The Definitive Debugging Guide for the cert-manager Webhook Pod +description: 'This guide helps you debug communication issues between the Kubernetes API server and the cert-manager webhook Pod.' +--- + +> Last verified: 8 Sept 2022 + +The cert-manager webhook is a pod that runs as part of your cert-manager +installation. When applying a manifest with `kubectl`, the Kubernetes API server +calls the cert-manager webhook over TLS to validate your manifests. This guide +helps you debug communication issues between the Kubernetes API server and the +cert-manager webhook pod. + +The error messages listed in this page are encountered while installing or +upgrading cert-manager, or shortly after installing or upgrading cert-manager +when trying to create a Certificate, Issuer, or any other cert-manager custom +resource. + +In the below diagram, we show the common pattern when debugging an issue with +the cert-manager webhook: when creating a cert-manager custom resource, the API +server connects over TLS to the cert-manager webhook pod. The red cross +indicates that the API server fails talking to the webhook. + +Diagram that shows a kubectl command that aims to create an issuer resource, and an arrow towards the Kubernetes API server, and an arrow between the API server and the webhook that indicates that the API server tries to connect to the webhook. This last arrow is crossed in red. + +The rest of this document presents error messages you may encounter. + +## Error: `connect: connection refused` + +> This issue was reported in 4 GitHub issues ([#2736](https://github.com/jetstack/cert-manager/issues/2736 "Getting WebHook Connection Refused error when using Azure DevOps Pipelines"), [#3133](https://github.com/jetstack/cert-manager/issues/3133 "Failed calling webhook webhook.cert-manager.io: connect: connection refused"), [#3445](https://github.com/jetstack/cert-manager/issues/3445 "Connection refused for cert-manager-webhook service"), [#4425](https://github.com/cert-manager/cert-manager/issues/4425 "Webhook error")), was reported in 1 GitHub issue in an external project ([`aws-load-balancer-controller#1563`](https://github.com/kubernetes-sigs/aws-load-balancer-controller/issues/1563 "Internal error occurred: failed calling webhook webhook.cert-manager.io, no endpoints available")), on Stack Overflow ([`serverfault#1076563`](https://web.archive.org/web/20210903183221/https://serverfault.com/questions/1076563/creating-issuer-for-kubernetes-cert-manager-is-causing-404-and-500-error "Creating issuer for kubernetes cert-manager is causing 404 and 500 error")), and was mentioned in 13 Slack messages that can be listed with the search `in:#cert-manager in:#cert-manager-dev ":443: connect: connection refused"`. This error message can also be found in other projects that are building webhooks ([`kubewarden-controller#110`](https://github.com/kubewarden/kubewarden-controller/issues/110 "Investigate failure on webhooks not ready when installing cert-manager from helm chart: connection refused")). + +Shortly after installing or upgrading cert-manager, you may hit this error when +creating a Certificate, Issuer, or any other cert-manager custom resource. For +example, creating an Issuer resource with the following command: + +```sh +kubectl apply -f- < 10.96.20.99 (webhook pod) TCP 59466 → 443 [SYN] +10.96.20.99 (webhook pod) -> 192.168.1.43 (apiserver) TCP 443 → 59466 [RST, ACK] +``` + +The `RST` packet is sent by the Linux kernel when nothing is listening to the +requested port. The `RST` packet can also be returned by one of the TCP hops, +e.g., a firewall, as detailed in the Stack Overflow page [What can be the +reasons of connection refused errors?](https://stackoverflow.com/a/2333446/3808537) + +Note that firewalls usually don't return an `RST` packet; they usually drop the +`SYN` packet entirely, and you end up with the error message `i/o timeout` or +`context deadline exceeded`. If that is the case, continue your investigation +with the section [Error: `i/o timeout` (connectivity issue)](#io-timeout) and [Error: `context +deadline exceeded`](#context-deadline-exceeded) respectively. + +Let's eliminate the possible causes from the closest to the source of the TCP +connection (the API server) to its destination (the pod `cert-manager-webhook`). + +Let's imagine that the name `cert-manager-webhook.cert-manager.svc` was resolved +to 10.43.183.232. This is a cluster IP. The control plane node, in which the API +server process runs, uses its iptables to rewrite the IP destination using the +pod IP. That might be the first problem: sometimes, no pod IP is associated with +a given cluster IP because the kubelet doesn't fill in the Endpoint resource +with pod IPs as long as the readiness probe doesn't work. + +Let us first check whether it is a problem with the Endpoint resource: + +```sh +kubectl get endpoints -n cert-manager cert-manager-webhook +``` + +A valid output would look like this: + +```text +NAME ENDPOINTS AGE +cert-manager-webhook 10.244.0.2:10250 27d ✅ +``` + +If you have this valid output and have the `connect: connection refused`, then +the issue is deeper in the networking stack. We won't dig into this case, but +you might want to use `tcpdump` and Wireshark to see whether traffic properly +flows from the API server to the node's host namespace. The traffic from the +host namespace to the pod's namespace already works fine since the kubelet was +already able to reach the readiness endpoint. + +Common issues include firewall dropping traffic from the control plane to +workers; for example, the API server on GKE is only allowed to talk to worker +nodes (which is where the cert-manager webhook is running) over port +`10250`. In EKS, your security groups might deny traffic from your control +plane VPC towards your workers VPC over TCP `10250`. + +If you see ``, it indicates that the cert-manager webhook is properly +running but its readiness endpoint can't be reached: + +```text +NAME ENDPOINTS AGE +cert-manager-webhook 236d ❌ +``` + +To fix ``, you will have to check whether the cert-manager-webhook +deployment is healthy. The endpoints stays at `` while the +cert-manager-webhook isn't marked as `healthy`. + +```sh +kubectl get pod -n cert-manager -l app.kubernetes.io/name=webhook +``` + +You should see that the pod is `Running`, and that the number of containers that +are ready is `0/1`: + +```text +NAME READY STATUS RESTARTS AGE +cert-manager-76578c9687-24kmr 0/1 Running 7 (8h ago) 28d ❌ +``` + +We won't be detailing the case where you get `1/1` and `Running`, since it would +indicate an inconsistent state in Kubernetes. + +Continuing with `0/1`, that means the readiness endpoint isn't answering. When +that happens, no endpoint is created. The next step is to figure out why the +readiness endpoint isn't answering. Let us see which port the kubelet is using +when hitting the readiness endpoint: + +```sh +kubectl -n cert-manager get deploy cert-manager-webhook -oyaml | grep -A5 readiness +``` + +In our example, the port that the kubelet will try to hit is 6080: + +```yaml +readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 6080 # ✨ + scheme: HTTP +``` + +Now, let us port-forward to that port and see if `/healthz` works. In a shell +session, run: + +```sh +kubectl -n cert-manager port-forward deploy/cert-manager-webhook 6080 +``` + +In another shell session, run: + +```sh +curl -sS --dump-header - 127.0.0.1:6080/healthz +``` + +The happy output is: + +```http +HTTP/1.1 200 OK ✅ +Date: Tue, 07 Jun 2022 17:16:56 GMT +Content-Length: 0 +``` + +If the readiness endpoint doesn't work, you will see: + +```text +curl: (7) Failed to connect to 127.0.0.1 port 6080 after 0 ms: Connection refused ❌ +``` + +At this point, verify that the readiness endpoint is configured on that same +port. Let us see the logs to check that our webhook is listening on 6080 for its +readiness endpoint: + +```console +$ kubectl logs -n cert-manager -l app.kubernetes.io/name=webhook | head -10 +I0607 webhook.go:129] "msg"="using dynamic certificate generating using CA stored in Secret resource" +I0607 server.go:133] "msg"="listening for insecure healthz connections" "address"=":6081" ❌ +I0607 server.go:197] "msg"="listening for secure connections" "address"=":10250" +I0607 dynamic_source.go:267] "msg"="Updated serving TLS certificate" +... +``` + +In the above example, the issue was a misconfiguration of the readiness port. In +the webhook deployment, the argument `--healthz-port=6081` was mismatched with +the readiness configuration. + + +## Error: `i/o timeout` (connectivity issue) + +> This error message was reported 26 times on Slack. To list these messages, do a search with `in:#cert-manager in:#cert-manager-dev "443: i/o timeout"`. The error message was reported in 2 GitHub issues ([#2811](https://github.com/cert-manager/cert-manager/issues/2811 "i/o timeout from apiserver when connecting to webhook on k3s"), [#4073](https://github.com/cert-manager/cert-manager/issues/4073 "Internal error occurred: failed calling webhook")) + +```text +Error from server (InternalError): error when creating "STDIN": Internal error occurred: + failed calling webhook "webhook.cert-manager.io": failed to call webhook: + Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": + dial tcp 10.0.0.69:443: i/o timeout +``` + +When the API server tries to talk to the cert-manager webhook, the `SYN` packet +is never answered, and the connection times out. If we were to run tcpdump +inside the webhook's net namespace, we would see: + +```text +192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP 44772 → 443 [SYN] +192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP [TCP Retransmission] 44772 → 443 [SYN] +192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP [TCP Retransmission] 44772 → 443 [SYN] +192.168.1.43 (apiserver) -> 10.0.0.69 (webhook pod) TCP [TCP Retransmission] 44772 → 443 [SYN] +``` + +This issue is caused by the `SYN` packet being dropped somewhere. + + +### Cause 1: GKE Private Cluster + +The default Helm configuration should work with GKE private clusters, but +changing `securePort` might break it. + +For context, unlike public GKE clusters where the control plane can freely talk +to pods over any TCP port, the control plane in private GKE clusters can only +talk to the pods in worker nodes over TCP port `10250` and `443`. These two open +ports refer to the `containerPort` inside the pod, not the port called `port` in +the Service resource. + +For it to work, the `containerPort` inside the Deployment must match either +`10250` or `443`; `containerPort` is configured by the Helm value +`webhook.securePort`. By default, `webhook.securePort` is set to `10250`. + +To see if something is off with the `containerPort`, let us start looking at the +Service resource: + +```sh +kubectl get svc -n cert-manager cert-manager-webhook -oyaml +``` + +Looking at the output, we see that the `targetPort` is set to `"https"`: + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: cert-manager-webhook +spec: + ports: + - name: https + port: 443 # ❌ This port is not the cause. + protocol: TCP + targetPort: "https" # 🌟 This port might be the cause. +``` + +The reason the above `port: 443` can't be the cause is because kube-proxy, which +also runs on the control plane node, translates the webhook's cluster IP to a +pod IP, and also translates the above `port: 443` to the value in +`containerPort`. + +To see how what is behind the target port `"https"`, we look at the +Deployment resource: + +```sh +kubectl get deploy -n cert-manager cert-manager-webhook -oyaml | grep -A3 ports: +``` + +The output shows that the `containerPort` is not set to `10250`, meaning that +a new firewall rule will have to be added in Google Cloud. + +```yaml + ports: + - containerPort: 12345 # 🌟 This port matches neither 10250 nor 443. + name: https + protocol: TCP +``` + +To recap, if the above `containerPort` is something other than `443` or `10250` and +you prefer not changing `containerPort` to `10250`, you will have to add a +new firewall rule. You can read the section [Adding a firewall rule in a +GKE private +cluster](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules) +in the Google documentation. + +For context, the reason we did not default `securePort` to `443` is because +binding to `443` requires one additional Linux capability +(`NET_BIND_SERVICE`); on the other side, `10250` doesn't require any +additional capability. + +### Cause 2: EKS on a custom CNI + +If you are on EKS and you are using a custom CNI such as Weave or Calico, +the Kubernetes API server (which is in its own node) might not be able to +reach the webhook pod. This happens because the control plane cannot be +configured to run on a custom CNI on EKS, meaning that the CNIs cannot +enable connectivity between the API server and the pods running in the +worker nodes. + +Supposing that you are using Helm, the workaround is to add the following +value in your `values.yaml` file: + +```yaml +webhook: + hostNetwork: true + securePort: 10260 +``` + +Or if you are using Helm from the command-line, use the following flag: + +```sh +--set webhook.hostNetwork=true --set webhook.securePort=10260 +``` + +By setting `hostNetwork` to `true`, the webhook pod will be run in the +host's network namespace. By running in the host's network namespace, the +webhook pod becomes accessible over the node's IP, which means you will +work around the fact that kube-apiserver can't reach any pod IPs nor +cluster IPs. + +By setting `securePort` to `10260` instead of relying on the default value +(which is `10250`), you will prevent a conflict between the webhook and the +kubelet. The kubelet, which is an agent that runs on every Kubernetes +worker node and runs directly on the host, uses the port `10250` to +expose its internal API to kube-apiserver. + +To understand how `hostnetwork` and `securePort` interact, we have to look +at how the TCP connection is established. When the kube-apiserver process +tries to connect to the webhook pod, kube-proxy (which also runs on control +plane nodes, even without a CNI) kicks in and translates the webhook's +cluster IP to the webhook's host IP: + +```diagram + https://cert-manager-webhook.cert-manager.svc:443/validate + | + |Step 1: resolve to the cluster IP + v + https://10.43.103.211:443/validate + | + |Step 2: send TCP packet + v + src: 172.28.0.1:43021 + dst: 10.43.103.211:443 + | + |Step 3: kube-proxy rewrite (cluster IP to host IP) + v + src: 172.28.0.1:43021 + dst: 172.28.0.2:10260 + | + | control-plane node + | (host IP: 172.28.0.1) +------------|-------------------------------------------------- + | (host IP: 172.28.0.2) + v worker node + +-------------------+ + | webhook pod | + | listens on | + | 172.28.0.2:10260 | + +-------------------+ +``` + +The reason `10250` is used as the default `securePort` is because it works +around another limitation with GKE Private Clusters, as detailed in the +above section [GKE Private Cluster](#gke-private-cluster). + +### Cause 3: Network Policies, Calico + +Assuming that you are using the Helm chart and that you are using the +default value of `webhook.securePort` (which is `10250`), and that you are +using a network policy controller such as Calico, check that there exists a +policy allowing traffic from the API server to the webhook pod over TCP +port `10250`. + +### Cause 4: EKS and Security Groups + +Assuming that you are using the Helm chart and that you are using the +default value of `webhook.securePort` (which is `10250`), you might want to +check that your AWS Security Groups allow TCP traffic over `10250` from the +control plane's VPC to the workers VPC. + +### Other causes + +If none of the above causes apply, you will need to figure out why the +webhook is unreachable. + +To debug reachability issues (i.e., packets being dropped), we advise to +use `tcpdump` along with Wireshark at every TCP hop. You can follow the +article [Debugging Kubernetes Networking: my `kube-dns` is not +working!](https://maelvls.dev/debugging-kubernetes-networking/) to learn +how to use `tcpdump` with Wireshark to debug networking issues. + +## Error: `x509: certificate is valid for xxx.internal, not cert-manager-webhook.cert-manager.svc` (EKS with Fargate pods) + +```text +Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + x509: certificate is valid for ip-192-168-xxx-xxx.xxx.compute.internal, + not cert-manager-webhook.cert-manager.svc +``` + +> This issue was first reported in +> [#3237](https://github.com/cert-manager/cert-manager/issues/3237 "Can't +> create an issuer when cert-manager runs on EKS in Fargate pods (AWS)"). + +This is probably because you are running on EKS with Fargate enabled. +Fargate creates a microVM per pod, and the VM's kernel is used to run the +container in its own namespace. The problem is that each microVM gets its +own kubelet. As for any Kubernetes node, the VM's port `10250` is listened to +by a kubelet process. And `10250` is also the port that the cert-manager +webhook listens on. + +But that's not a problem: the kubelet process and the cert-manager webhook +process are running in two separate network namespaces, and ports don't +clash. That's the case both in traditional Kubernetes nodes, as well as +inside a Fargate microVM. + +The problem arises when the API server tries hitting the Fargate pod: the +microVM's host net namespace is configured to port-forward every possible port +for maximum compatibility with traditional pods, as demonstrated in the Stack +Overflow page [EKS Fargate connect to local kubelet][66445207]. But the port +`10250` is already used by the microVM's kubelet, so anything hitting this port +won't be port-forwarded and will hit the kubelet instead. + +[66445207]: https://stackoverflow.com/questions/66445207 "EKS Fargate connect to local kubelet" + +To sum up, the cert-manager webhook looks healthy and is able to listen to port +`10250` as per its logs, but the microVM's host does not port-forward `10250` to the +webhook's net namespace. That's the reason you see a message about an unexpected +domain showing up when doing the TLS handshake: although the cert-manager +webhook is properly running, the kubelet is the one responding to the API +server. + +This is a limitation of Fargate's microVMs: the IP of the pod and the IP of the +node are the same. It gives you the same experience as traditional pods, but it +poses networking challenges. + +To fix the issue, the trick is to change the port the cert-manager webhook is +listening on. Using Helm, we can use the parameter `webhook.securePort`: + +```sh +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.8.0 \ + --set webhook.securePort=10260 +``` + +## Error: `service "cert-managercert-manager-webhook" not found` + +```text +Error from server (InternalError): error when creating "test-resources.yaml": Internal error occurred: + failed calling webhook "webhook.cert-manager.io": failed to call webhook: + Post "https://cert-managercert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": + service "cert-managercert-manager-webhook" not found +``` + +> This error was reported in 2 GitHub issues ([#3195](https://github.com/jetstack/cert-manager/issues/3195 "service cert-manager-webhook not found"), +> [#4999](https://github.com/cert-manager/cert-manager/issues/4999 "Verification on 1.7.2 fails (Kubectl apply), service cert-manager-webhook not found")). + +We do not know the cause of this error, please comment on one of the GitHub +issues above if you happen to come across it. + +## Error: `no endpoints available for service "cert-manager-webhook"` (OVHCloud) + +```text +Error: INSTALLATION FAILED: Internal error occurred: + failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + no endpoints available for service "cert-manager-webhook" +``` + +> This issue was first reported once in Slack +> ([1](https://kubernetes.slack.com/archives/C4NV3DWUC/p1634118489064400?thread_ts=1592676867.472700&cid=C4NV3DWUC)). + +This error is rare and was only seen in OVHcloud managed Kubernetes clusters, +where the etcd resource quota is quite low. etcd is the database where your +Kubernetes resources (such as pods and deployments) are stored. OVHCloud limits +the disk space used by your resources in etcd. When the limit is reached, the +whole cluster starts behaving erratically and one symptom is that Endpoint +resources aren't created by the kubelet. + +To verify that it is in fact a problem of quota, you should be able to see the +following messages in your kube-apiserver logs: + +```sh +rpc error: code = Unknown desc = ETCD storage quota exceeded +rpc error: code = Unknown desc = quota computation: etcdserver: not capable +rpc error: code = Unknown desc = The OVHcloud storage quota has been reached +``` + +The workaround is to remove some resources such as CertificateRequest resources +to get under the limit, as explained in OVHCloud's [ETCD Quotas error, +troubleshooting](https://docs.ovh.com/gb/en/kubernetes/etcd-quota-error/) page. + +## Error: `x509: certificate has expired or is not yet valid` + +> This error message was reported once in Slack +> ([1](https://kubernetes.slack.com/archives/C4NV3DWUC/p1618579222346800)). + +When using `kubectl apply`: + +```text +Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://kubernetes.default.svc:443/apis/webhook.cert-manager.io/v1beta1/mutations?timeout=30s: + x509: certificate has expired or is not yet valid +``` + +> This error message was reported once in Slack +([1](https://kubernetes.slack.com/archives/C4NV3DWUC/p1618579222346800)). + +Please answer to the above Slack message since we are still unsure as to what +may cause this issue; to get access to the Kubernetes Slack, visit +[https://slack.k8s.io/](https://slack.k8s.io/). + +## Error: `net/http: request canceled while waiting for connection` + +```text +Error from server (InternalError): error when creating "STDIN": + Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) +``` + +> This error message was reported once in Slack +([1](https://kubernetes.slack.com/archives/C4NV3DWUC/p1632849763397100)). + + +## Error: `context deadline exceeded` + +> This error message was reported in GitHub issues ([2319](https://github.com/cert-manager/cert-manager/issues/2319 "Documenting context deadline exceeded errors relating to the webhook, on bare metal"), [2706](https://github.com/cert-manager/cert-manager/issues/2706 "") [5189](https://github.com/cert-manager/cert-manager/issues/5189 "Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s: context deadline exceeded"), [5004](https://github.com/cert-manager/cert-manager/issues/5004 "After installing cert-manager using kubectl, cmctl check api fails with https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s: context deadline exceeded")), and once [on Stack Overflow](https://stackoverflow.com/questions/72059332/how-can-i-fix-failed-calling-webhook-webhook-cert-manager-io). + +This error appears with cert-manager 0.12 and above when trying to apply an +Issuer or any other cert-manager custom resource after having installed or +upgraded cert-manager: + +```text +Error from server (InternalError): error when creating "STDIN": + Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + context deadline exceeded +``` + +> ℹ️ In older releases of cert-manager (0.11 and below), the webhook relied on +> the [APIService +> mechanism](https://kubernetes.io/docs/tasks/extend-kubernetes/setup-extension-api-server/), +> and the message looked a bit different but the cause was the same: +> +> ```text +> Error from server (InternalError): error when creating "STDIN": +> Internal error occurred: failed calling webhook "webhook.certmanager.k8s.io": +> Post https://kubernetes.default.svc:443/apis/webhook.certmanager.k8s.io/v1beta1/mutations?timeout=30s: +> context deadline exceeded +> ``` + +> ℹ️ The message `context deadline exceeded` also appears when using `cmctl +> check api`. The cause is identical, you can continue reading this section to +> debug it. +> +> ```text +> Not ready: Internal error occurred: failed calling webhook "webhook.cert-manager.io": failed to call webhook: +> Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": +> context deadline exceeded +> ``` + +The trouble with the message `context deadline exceeded` is that it obfuscates +the part of the HTTP connection that timed out. When this message appears, we +can't tell which part of the HTTP interaction timed out. It might be the DNS +resolution, the TCP handshake, the TLS handshake, sending the HTTP request or +receiving the HTTP response. + +> ℹ️ For context, the query parameter `?timeout=30s` that you can see in the +> above error messages is a timeout that the API server decides when calling the +> webhook. It is often set to 10 or 30 seconds. + +The following diagram shows what are the three errors that may be hidden behind +the all-catching "context deadline exceeded" error message, represented by the +outer box, that is usually thrown after 30 seconds: + + + +```diagram + context deadline exceeded + | + 30 seconds | + timeout v + +-------------------------------------------------------------------------+ + | | + | i/o timeout | + | | net/http: TLS handshake timeout | + | 10 seconds | | | + | timeout v | | + |------------+ 30 seconds | net/http: request canceled | + |TCP | timeout v while awaiting headers | + |handshake +---------------------+ | | + |------------| TLS | | | + | | handshake +------------+ 10 seconds | | + | +---------------------| sending | timeout v | + | | request +------------+ | + | +------------|receiving |------+ | + | |resp. header| recv.| | + | +------------+ resp.| | + | | body +-----+ + | +------|other| + | |logic| + | +-----+ + +-------------------------------------------------------------------------+ + <----------> <----------------------------------------------> + connectivity webhook-side + issue issue +``` + +In the rest of the section, we will be trying to trigger one of the three "more +specific" errors: + +- `i/o timeout` is the TCP handshake timeout and comes from + [`DialTimeout`](https://pkg.go.dev/net#DialTimeout) in the Kubernetes + apiserver. The name resolution may be the cause, but usually, this message + appears after the API server sent the `SYN` packet and waited for 10 seconds + for the `SYN-ACK` packet to be received from the cert-manager webhook. +- `net/http: request canceled while waiting for connection (Client.Timeout + exceeded while awaiting headers)` is the HTTP response timeout and comes from + [here](https://github.com/kubernetes/kubernetes/blob/abba1492f/staging/src/k8s.io/apiserver/pkg/util/webhook/webhook.go#L96-L101) + and is configured to [30 + seconds](https://github.com/kubernetes/kubernetes/blob/abba1492f/staging/src/k8s.io/apiserver/pkg/util/webhook/webhook.go#L36-L38). + The Kubernetes API server already sent the HTTP request is is waiting for the + HTTP response headers (e.g., `HTTP/1.1 200 OK`). +- `net/http: TLS handshake timeout` is when the TCP handshake is done, and the + Kubernetes API server sent the initial TLS handshake packet (`ClientHello`) + and waited for 10 seconds for the cert-manager webhook to answer with the + `ServerHello` packet. + +We can sort these three messages in two categories: either it is a connectivity +issue (`SYN` is dropped), or it is a webhook issue (i.e., the TLS certificate is +wrong, or the webhook is not returning any HTTP response): + +| Timeout message | Category | +|-----------------------------------------------------|--------------------| +| `i/o timeout` | connectivity issue | +| `net/http: TLS handshake timeout` | webhook-side issue | +| `net/http: request canceled while awaiting headers` | webhook-side issue | + +The first step is to rule out a webhook-side issue. In your shell session, run +the following: + +```sh +kubectl -n cert-manager port-forward deploy/cert-manager-webhook 10250 +``` + +In another shell session, check that you can reach the webhook: + +```sh +curl -vsS --resolve cert-manager-webhook.cert-manager.svc:10250:127.0.0.1 \ + --service-name cert-manager-webhook-ca \ + --cacert <(kubectl -n cert-manager get secret cert-manager-webhook-ca -ojsonpath='{.data.ca\.crt}' | base64 -d) \ + https://cert-manager-webhook.cert-manager.svc:10250/validate 2>&1 -d@- <<'EOF' | sed '/^* /d; /bytes data]$/d; s/> //; s/< //' +{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"requestKind":{"group":"cert-manager.io","version":"v1","kind":"Certificate"},"requestResource":{"group":"cert-manager.io","version":"v1","resource":"certificates"},"name":"foo","namespace":"default","operation":"CREATE","object":{"apiVersion":"cert-manager.io/v1","kind":"Certificate","spec":{"dnsNames":["foo"],"issuerRef":{"group":"cert-manager.io","kind":"Issuer","name":"letsencrypt"},"secretName":"foo","usages":["digital signature"]}}}} +EOF +``` + +The happy output looks like this: + +```http +POST /validate HTTP/1.1 +Host: cert-manager-webhook.cert-manager.svc:10250 +User-Agent: curl/7.83.0 +Accept: */* +Content-Length: 1299 +Content-Type: application/x-www-form-urlencoded + +HTTP/1.1 200 OK +Date: Wed, 08 Jun 2022 14:52:21 GMT +Content-Length: 2029 +Content-Type: text/plain; charset=utf-8 + +... +"response": { + "uid": "", + "allowed": true +} +``` + +If the response shows `200 OK`, we can rule out a webhook-side issue. Since the +initial error message was `context deadline exceeded` and not an apiserver-side +issue such as `x509: certificate signed by unknown authority` or `x509: +certificate has expired or is not yet valid`, we can conclude that the problem +is a connectivity issue: the Kubernetes API server isn't able to establish a TCP +connection to the cert-manager webhook. Please follow the instructions in the +section [Error: `i/o timeout` (connectivity issue)](#io-timeout) above to +continue debugging. + +## Error: `net/http: TLS handshake timeout` + +> This error message was reported in 1 GitHub issue ([#2602](https://github.com/cert-manager/cert-manager/issues/2602 "Internal error occurred: failed calling webhook webhook.cert-manager.io: Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: net/http: TLS handshake timeout")). + +```text +Error from server (InternalError): error when creating "STDIN": + Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + net/http: TLS handshake timeout +``` + +Looking at the [above diagram](#diagram), this error message indicates that the +Kubernetes API server successfully established a TCP connection to the pod IP +associated with the cert-manager webhook. The TLS handshake timeout means that +the cert-manager webhook process isn't the one ending the TCP connection: there +is some HTTP proxy in between that is probably waiting for a plain HTTP request +instead a `ClientHello` packet. + +We do not know the cause of this error. Please comment on the above GitHub +issue if you notice this error. + +## Error: `HTTP probe failed with statuscode: 500` + +> This error message was reported in 2 GitHub issue ([#3185](https://github.com/cert-manager/cert-manager/issues/3185 "kubectl install cert-manager: Readiness probe failed: HTTP probe failed with statuscode: 500"), [#4557](https://github.com/cert-manager/cert-manager/issues/4557 "kubectl install cert-manager: Readiness probe failed: HTTP probe failed with statuscode: 500")). + +The error message is visible as an event on the cert-manager webhook: + +```text +Warning Unhealthy (x13 over 15s) kubelet, node83 + Readiness probe failed: HTTP probe failed with statuscode: 500 +``` + +We do not know the cause of this error. Please comment on the above GitHub +issue if you notice this error. + +## Error: `Service Unavailable` + +> This error was reported in 1 GitHub issue ([#4281](https://github.com/cert-manager/cert-manager/issues/4281 "Can't deploy Issuer, Service Unavailable")) + +```text +Error from server (InternalError): error when creating "STDIN": Internal error occurred: + failed calling webhook "webhook.cert-manager.io": + Post "https://my-cert-manager-webhook.default.svc:443/mutate?timeout=10s": + Service Unavailable +``` + +The above message appears in Kubernetes clusters using the Weave CNI. + +We do not know the cause of this error. Please comment on the above GitHub +issue if you notice this error. + +## Error: `failed calling admission webhook: the server is currently unable to handle the request` + +> This issue was reported in 4 GitHub issues ([1369](https://github.com/cert-manager/cert-manager/issues/1369 "the server is currently unable to handle the request"), [1425](https://github.com/cert-manager/cert-manager/issues/1425 "Verifying Install: failed calling admission webhook (Azure, GKE private cluster)") [3542](https://github.com/cert-manager/cert-manager/issues/3542 "SSL Certificate Manager has got expired, we need to renew SSL certificate in existing ClusterIssuer Kubernetes Service (AKS)"), [4852](https://github.com/cert-manager/cert-manager/issues/4852 "error: unable to retrieve the complete list of server APIs: webhook.cert-manager.io/v1beta1: the server is currently unable to handle the request (AKS)")) + +```text +Error from server (InternalError): error when creating "test-resources.yaml": Internal error occurred: + failed calling admission webhook "issuers.admission.certmanager.k8s.io": + the server is currently unable to handle the request +``` + +We do not know the cause of this error. Please comment in one of the above +GitHub issues if you are able to reproduce this error. + +## Error: `x509: certificate signed by unknown authority` + +> Reported in GitHub issues +> ([2602](https://github.com/cert-manager/cert-manager/issues/2602#issuecomment-606474055 "x509: certificate signed by unknown authority")) + +When installing or upgrading cert-manager and using a namespace that is not +`cert-manager`: + +```text +Error: UPGRADE FAILED: release core-l7 failed, and has been rolled back due to atomic being set: + failed to create resource: conversion webhook for cert-manager.io/v1alpha3, Kind=ClusterIssuer failed: + Post https://cert-manager-webhook.core-l7.svc:443/convert?timeout=30s: + x509: certificate signed by unknown authority +``` + +A very similar error message may show when creating an Issuer or any other +cert-manager custom resource: + +```text +Internal error occurred: failed calling webhook "webhook.cert-manager.io": + Post https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s: + x509: certificate signed by unknown authority` +``` + +With `cmctl install` and `cmctl check api`, you might see the following error +message: + +```text +2022/06/06 15:36:30 Not ready: the cert-manager webhook CA bundle is not injected yet + (Internal error occurred: conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: + Post "https://-cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": + x509: certificate signed by unknown authority) +``` + +If you are using cert-manager 0.14 and below with Helm, and that you are +installing in a namespace different from `cert-manager`, the CRD manifest had +the namespace name `cert-manager` hardcoded. You can see the hardcoded namespace +in the following annotation: + +```sh +kubectl get crd issuers.cert-manager.io -oyaml | grep inject +``` + +You will see the following: + +```yaml +cert-manager.io/inject-ca-from-secret: cert-manager/cert-manager-webhook-ca +# ^^^^^^^^^^^^ +# hardcoded +``` + +> **Note 1:** this bug in the cert-manager Helm chart was [was +fixed](https://github.com/cert-manager/cert-manager/commit/f33beefc) in +cert-manager 0.15. +> +> **Note 2:** since cert-manager 1.6, this annotation is [no longer +> used](https://github.com/cert-manager/cert-manager/pull/4841) on the +> cert-manager CRDs since conversion is no longer needed. + +The solution, if you are still using cert-manager 0.14 or below, is to render +the manifest using `helm template`, then edit the annotation to use the correct +namespace, and then use `kubectl apply` to install cert-manager. + +If you are using cert-manager 1.6 and below, the issue might be due to the +cainjector being stuck trying to inject the self-signed certificate that the +cert-manager webhook created and stored in the Secret resource +`cert-manager-webhook-ca` into the `spec.caBundle` field of the cert-manager +CRDs. The first step is to check whether the cainjector is running with no +problem: + +```console +$ kubectl -n cert-manager get pods -l app.kubernetes.io/name=cainjector +NAME READY STATUS RESTARTS AGE +cert-manager-cainjector-5c55bb7cb4-6z4cf 1/1 Running 11 (31h ago) 28d +``` + +Looking at the logs, you will be able to tell if the leader election worked. It +can take up to one minute for the leader election work to complete. + +```console +I0608 start.go:126] "starting" version="v1.8.0" revision="e466a521bc5455def8c224599c6edcd37e86410c" +I0608 leaderelection.go:248] attempting to acquire leader lease kube-system/cert-manager-cainjector-leader-election... +I0608 leaderelection.go:258] successfully acquired lease kube-system/cert-manager-cainjector-leader-election +I0608 controller.go:186] cert-manager/secret/customresourcedefinition/controller/controller-for-secret-customresourcedefinition "msg"="Starting Controller" +I0608 controller.go:186] cert-manager/certificate/customresourcedefinition/controller/controller-for-certificate-customresourcedefinition "msg"="Starting Controller" +I0608 controller.go:220] cert-manager/secret/customresourcedefinition/controller/controller-for-secret-customresourcedefinition "msg"="Starting workers" "worker count"=1 +I0608 controller.go:220] cert-manager/certificate/customresourcedefinition/controller/controller-for-certificate-customresourcedefinition "msg"="Starting workers" "worker count"=1 +``` + +The happy output contains lines like this: + +```console +I0608 sources.go:184] cert-manager/secret/customresourcedefinition/generic-inject-reconciler + "msg"="Extracting CA from Secret resource" "resource_name"="issuers.cert-manager.io" "secret"="cert-manager/cert-manager-webhook-ca" +I0608 controller.go:178] cert-manager/secret/customresourcedefinition/generic-inject-reconciler + "msg"="updated object" "resource_name"="issuers.cert-manager.io" +``` + +Now, look for any message that indicates that the Secret resource that the +cert-manager webhook created can't be loaded. The two error messages that might +show up are: + +```text +E0608 sources.go:201] cert-manager/secret/customresourcedefinition/generic-inject-reconciler + "msg"="unable to fetch associated secret" "error"="Secret \"cert-manager-webhook-caq\" not found" +``` + +The following message indicates that the given CRD has been skipped because the +annotation is missing. You can ignore these messages: + +```text +I0608 controller.go:156] cert-manager/secret/customresourcedefinition/generic-inject-reconciler + "msg"="failed to determine ca data source for injectable" "resource_name"="challenges.acme.cert-manager.io" +``` + +If nothing seems wrong with the cainjector logs, you will want to check that the +`spec.caBundle` field in the validation, mutation, and conversion configurations +are correct. The Kubernetes API server uses the contents of that field to trust +the cert-manager webhook. The `caBundle` contains the self-signed CA created by +the cert-manager webhook when it started. + +```console +$ kubectl get validatingwebhookconfigurations cert-manager-webhook -ojson | jq '.webhooks[].clientConfig' +{ + "caBundle": "LS0tLS1...LS0tLS0K", + "service": { + "name": "cert-manager-webhook", + "namespace": "cert-manager", + "path": "/validate", + "port": 443 + } +} +``` + +```console +$ kubectl get mutatingwebhookconfigurations cert-manager-webhook -ojson | jq '.webhooks[].clientConfig' +{ + "caBundle": "LS0tLS1...RFLS0tLS0K", + "service": { + "name": "cert-manager-webhook", + "namespace": "cert-manager", + "path": "/validate", + "port": 443 + } +} +``` + +Let us see the contents of the `caBundle`: + +```console +$ kubectl get mutatingwebhookconfigurations cert-manager-webhook -ojson \ + | jq '.webhooks[].clientConfig.caBundle' -r | base64 -d \ + | openssl x509 -noout -text -in - + +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + ee:8f:4f:c8:55:7b:16:76:d8:6a:a2:e5:94:bc:7c:6b + Signature Algorithm: ecdsa-with-SHA384 + Issuer: CN = cert-manager-webhook-ca + Validity + Not Before: May 10 16:13:37 2022 GMT + Not After : May 10 16:13:37 2023 GMT + Subject: CN = cert-manager-webhook-ca +``` + +Let us check that the contents of `caBundle` works for connecting to the +webhook: + +```console +$ kubectl -n cert-manager get secret cert-manager-webhook-ca -ojsonpath='{.data.ca\.crt}' \ + | base64 -d | openssl x509 -noout -text -in - + +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + ee:8f:4f:c8:55:7b:16:76:d8:6a:a2:e5:94:bc:7c:6b + Signature Algorithm: ecdsa-with-SHA384 + Issuer: CN = cert-manager-webhook-ca + Validity + Not Before: May 10 16:13:37 2022 GMT + Not After : May 10 16:13:37 2023 GMT + Subject: CN = cert-manager-webhook-ca +``` + +Our final test is to try to connect to the webhook using this trust bundle. Let +us port-forward to the webhook pod: + +```sh +kubectl -n cert-manager port-forward deploy/cert-manager-webhook 10250 +``` + +In another shell session, send a `/validate` HTTP request with the following +command: + +```sh +curl -vsS --resolve cert-manager-webhook.cert-manager.svc:10250:127.0.0.1 \ + --service-name cert-manager-webhook-ca \ + --cacert <(kubectl get validatingwebhookconfigurations cert-manager-webhook -ojson | jq '.webhooks[].clientConfig.caBundle' -r | base64 -d) \ + https://cert-manager-webhook.cert-manager.svc:10250/validate 2>&1 -d@- <<'EOF' | sed '/^* /d; /bytes data]$/d; s/> //; s/< //' +{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"requestKind":{"group":"cert-manager.io","version":"v1","kind":"Certificate"},"requestResource":{"group":"cert-manager.io","version":"v1","resource":"certificates"},"name":"foo","namespace":"default","operation":"CREATE","object":{"apiVersion":"cert-manager.io/v1","kind":"Certificate","spec":{"dnsNames":["foo"],"issuerRef":{"group":"cert-manager.io","kind":"Issuer","name":"letsencrypt"},"secretName":"foo","usages":["digital signature"]}}}} +EOF +``` + +You should see a successful HTTP request and response: + +```http +POST /validate HTTP/1.1 +Host: cert-manager-webhook.cert-manager.svc:10250 +User-Agent: curl/7.83.0 +Accept: */* +Content-Length: 1299 +Content-Type: application/x-www-form-urlencoded + +HTTP/1.1 200 OK +Date: Wed, 08 Jun 2022 16:20:45 GMT +Content-Length: 2029 +Content-Type: text/plain; charset=utf-8 + +... +``` + +## Error: `cluster scoped resource "mutatingwebhookconfigurations/" is managed and access is denied` + +> This message was reported in GitHub issue +> [3717](https://github.com/cert-manager/cert-manager/issues/3717 "Cannot +> install on GKE autopilot cluster due to mutatingwebhookconfigurations access +> denied"). + +While installing cert-manager on GKE Autopilot, you will see the following +message: + +```text +Error: rendered manifests contain a resource that already exists. Unable to continue with install: + could not get information about the resource: + mutatingwebhookconfigurations.admissionregistration.k8s.io "cert-manager-webhook" is forbidden: + User "XXXX" cannot get resource "mutatingwebhookconfigurations" in API group "admissionregistration.k8s.io" at the cluster scope: + GKEAutopilot authz: cluster scoped resource "mutatingwebhookconfigurations/" is managed and access is denied +``` + +This error message will appear when using Kubernetes 1.20 and below with GKE +Autopilot. It is due to a [restriction on mutating admission webhooks in GKE +Autopilot](https://github.com/cert-manager/cert-manager/issues/3717). + +As of October 2021, the "rapid" Autopilot release channel has rolled out version +1.21 for Kubernetes masters. Installation via the Helm chart may end in an error +message but cert-manager is reported to be working by some users. Feedback and +PRs are welcome. + +## Error: `the namespace "kube-system" is managed and the request's verb "create" is denied` + +When installing cert-manager on GKE Autopilot with Helm, you will see the +following error message: + +```text +Not ready: the cert-manager webhook CA bundle is not injected yet +``` + +After this failure, you should still see the three pods happily running: + +```console +$ kubectl get pods -n cert-manager +NAME READY STATUS RESTARTS AGE +cert-manager-76578c9687-24kmr 1/1 Running 0 47m +cert-manager-cainjector-b7d47f746-4799n 1/1 Running 0 47m +cert-manager-webhook-7f788c5b6-mspnt 1/1 Running 0 47m +``` + +But looking at either of the logs, you will see the following error message: + +```text +E0425 leaderelection.go:334] error initially creating leader election record: + leases.coordination.k8s.io is forbidden: User "system:serviceaccount:cert-manager:cert-manager-webhook" + cannot create resource "leases" in API group "coordination.k8s.io" in the namespace "kube-system": + GKEAutopilot authz: the namespace "kube-system" is managed and the request's verb "create" is denied +``` + +That is due to a limitation of GKE Autopilot. It is not possible to create +resources in the `kube-system` namespace, and cert-manager uses the well-known +`kube-system` to manage the leader election. To get around the limitation, you +can tell Helm to use a different namespace for the leader election: + +```sh +helm install cert-manager jetstack/cert-manager --version 1.8.0 \ + --namespace cert-manager --create-namespace \ + --set global.leaderElection.namespace=cert-manager +``` diff --git a/content/v1.13-docs/tutorials/README.md b/content/v1.13-docs/tutorials/README.md new file mode 100644 index 0000000000..cc839b6b10 --- /dev/null +++ b/content/v1.13-docs/tutorials/README.md @@ -0,0 +1,38 @@ +--- +title: Tutorials +description: 'cert-manager tutorials: Overview' +--- + +Step-by-step tutorials are a great way to get started with cert-manager, and we provide a few +for you to learn from. Take a look! + +- [Securing Ingresses with NGINX-Ingress and cert-manager](./acme/nginx-ingress.md): Tutorial for deploying NGINX into your + cluster and securing incoming connections with a certificate from Let's Encrypt. +- [GKE + Ingress + Let's Encrypt](./getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md): + Learn how to deploy cert-manager on Google Kubernetes Engine and how to configure it to get certificates for Ingress, from Let's Encrypt. +- [AKS + LoadBalancer + Let's Encrypt](getting-started-aks-letsencrypt/README.md): + Learn how to deploy cert-manager on Azure Kubernetes Service (AKS) and how to configure it to get certificates for an HTTPS web server, from Let's Encrypt. +- [Backup and Restore Resources](./backup.md): Backup the cert-manager resources + in your cluster and then restore them. +- [Pomerium Ingress](./acme/pomerium-ingress.md): Tutorial on using the Pomerium Ingress Controller with cert-manager. +- [Issuing an ACME Certificate using DNS Validation](./acme/dns-validation.md): + Tutorial on how to resolve DNS ownership validation using DNS01 challenges. +- [Issuing an ACME Certificate using HTTP Validation](./acme/http-validation.md): + Tutorial on how to resolve DNS ownership validation using HTTP01 challenges. +- [Migrating from kube-lego](./acme/migrating-from-kube-lego.md): Tutorial on + how to migrate from the now deprecated kube-lego project. +- [Securing an EKS Cluster with Venafi](./venafi/venafi.md): Tutorial for + creating an EKS cluster and securing an NGINX deployment with a Venafi issued + certificate. +- [Securing an Istio service mesh with cert-manager](./istio-csr/istio-csr.md): Tutorial for + securing an Istio service mesh using a cert-manager issuer. +- [Syncing Secrets Across Namespaces](./syncing-secrets-across-namespaces.md): + Learn how to synchronize Kubernetes Secret resources across namespaces using extensions such as: reflector, kubed and kubernetes-replicator. +- [Obtaining SSL certificates with the ZeroSSL](./zerossl/zerossl.md): Tutorial describing usage of the ZeroSSL as external ACME server. + +### External Tutorials + +- A great AWS blog post on using cert-manager for end-to-end encryption in EKS. See [Setting up end-to-end TLS encryption on Amazon EKS](https://aws.amazon.com/blogs/containers/setting-up-end-to-end-tls-encryption-on-amazon-eks-with-the-new-aws-load-balancer-controller/) +- A full cert-manager installation demo on a GKE Cluster. See [How-To: Automatic SSL Certificate Management for your Kubernetes Application Deployment](https://medium.com/contino-engineering/how-to-automatic-ssl-certificate-management-for-your-kubernetes-application-deployment-94b64dfc9114) +- cert-manager installation on GKE Cluster using Workload Identity. See [Kubernetes, ingress-nginx, cert-manager & external-dns](https://blog.atomist.com/kubernetes-ingress-nginx-cert-manager-external-dns/) +- A video tutorial for beginners showing cert-manager in action. See [Free SSL for Kubernetes with cert-manager](https://www.youtube.com/watch?v=hoLUigg4V18) diff --git a/content/v1.13-docs/tutorials/acme/dns-validation.md b/content/v1.13-docs/tutorials/acme/dns-validation.md new file mode 100644 index 0000000000..bddf11097b --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/dns-validation.md @@ -0,0 +1,169 @@ +--- +title: DNS Validation +description: 'cert-manager turorials: Issuing an ACME certificate using DNS validation' +--- + +## Issuing an ACME certificate using DNS validation + +cert-manager can be used to obtain certificates from a CA using the +[ACME](https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment) +protocol. The ACME protocol supports various challenge mechanisms which are +used to prove ownership of a domain so that a valid certificate can be issued +for that domain. + +One such challenge mechanism is DNS01. With a DNS01 challenge, you prove +ownership of a domain by proving you control its DNS records. +This is done by creating a TXT record with specific content that proves you +have control of the domains DNS records. + +The following Issuer defines the necessary information to enable DNS validation. +You can read more about the Issuer resource in the [Issuer +docs](../../configuration/README.md). + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging + namespace: default +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: user@example.com + + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + + # ACME DNS-01 provider configurations + solvers: + # An empty 'selector' means that this solver matches all domains + - selector: {} + dns01: + cloudDNS: + # The ID of the GCP project + # reference: https://cert-manager.io/docs/tutorials/acme/dns-validation/ + project: $PROJECT_ID + # This is the secret used to access the service account + serviceAccountSecretRef: + name: clouddns-dns01-solver-svc-acct + key: key.json + + # We only use cloudflare to solve challenges for example.org. + # Alternative options such as 'matchLabels' and 'dnsZones' can be specified + # as part of a solver's selector too. + - selector: + dnsNames: + - example.org + dns01: + cloudflare: + email: my-cloudflare-acc@example.com + # !! Remember to create a k8s secret before + # kubectl create secret generic cloudflare-api-key-secret + apiKeySecretRef: + name: cloudflare-api-key-secret + key: api-key +``` + + +We have specified the ACME server URL for Let's Encrypt's [staging +environment](https://letsencrypt.org/docs/staging-environment/). The staging +environment will not issue trusted certificates but is used to ensure that the +verification process is working properly before moving to production. Let's +Encrypt's production environment imposes much stricter [rate +limits](https://letsencrypt.org/docs/rate-limits/), so to reduce the chance of +you hitting those limits it is highly recommended to start by using the staging +environment. To move to production, simply create a new Issuer with the URL set +to `https://acme-v02.api.letsencrypt.org/directory`. + +The first stage of the ACME protocol is for the client to register with the +ACME server. This phase includes generating an asymmetric key pair which is +then associated with the email address specified in the Issuer. Make sure to +change this email address to a valid one that you own. It is commonly used to +send expiry notices when your certificates are coming up for renewal. The +generated private key is stored in a Secret named `letsencrypt-staging`. + +The `dns01` stanza contains a list of DNS01 providers that can be used to +solve DNS challenges. Our Issuer defines two providers. This gives us a choice +of which one to use when obtaining certificates. + +More information about the DNS provider configuration, including a list of +supported providers, can be found [in the DNS01 reference docs](../../configuration/acme/dns01/README.md). + +Once we have created the above Issuer we can use it to obtain a certificate. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: default +spec: + secretName: example-com-tls + issuerRef: + name: letsencrypt-staging + dnsNames: + - '*.example.com' + - example.com + - example.org +``` + +The Certificate resource describes our desired certificate and the possible +methods that can be used to obtain it. You can obtain certificates for wildcard +domains just like any other. Make sure to wrap wildcard domains with asterisks +in your YAML resources, to avoid formatting issues. If you specify both +`example.com` and `*.example.com` on the same Certificate, it will take slightly +longer to perform validation as each domain will have to be validated one after +the other. You can learn more about the Certificate resource in the +[docs](../../usage/README.md). If the certificate is obtained successfully, the +resulting key pair will be stored in a secret called `example-com-tls` in the +same namespace as the Certificate. + +The certificate will have a common name of `*.example.com` and the [Subject +Alternative Names +(SANs)](https://en.wikipedia.org/wiki/Subject_Alternative_Name) will be +`*.example.com`, `example.com` and `example.org`. + +In our Certificate we have referenced the `letsencrypt-staging` Issuer above. +The Issuer must be in the same namespace as the Certificate. If you want to +reference a `ClusterIssuer`, which is a cluster-scoped version of an Issuer, you +must add `kind: ClusterIssuer` to the `issuerRef` stanza. + +For more information on `ClusterIssuers`, read the +[issuer concepts](../../concepts/issuer.md). + +The `acme` stanza defines the configuration for our ACME challenges. Here we +have defined the configuration for our DNS challenges which will be used to +verify domain ownership. For each domain mentioned in a `dns01` stanza, +cert-manager will use the provider's credentials from the referenced Issuer to +create a TXT record called `_acme-challenge`. This record will then be verified +by the ACME server in order to issue the certificate. Once domain ownership has +been verified, any cert-manager affected records will be cleaned up. + +> Note: It is your responsibility to ensure the selected provider is +> authoritative for your domain. + +After creating the above Certificate, we can check whether it has been obtained +successfully using `kubectl describe`: + +```bash +$ kubectl describe certificate example-com +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CreateOrder 57m cert-manager Created new ACME order, attempting validation... + Normal DomainVerified 55m cert-manager Domain "*.example.com" verified with "dns-01" validation + Normal DomainVerified 55m cert-manager Domain "example.com" verified with "dns-01" validation + Normal DomainVerified 55m cert-manager Domain "example.org" verified with "dns-01" validation + Normal IssueCert 55m cert-manager Issuing certificate... + Normal CertObtained 55m cert-manager Obtained certificate from ACME server + Normal CertIssued 55m cert-manager Certificate issued successfully +``` + +You can also check whether issuance was successful with `kubectl get secret +example-com-tls -o yaml`. You should see a base64 encoded signed TLS key pair. + +Once our certificate has been obtained, cert-manager will periodically check its +validity and attempt to renew it if it gets close to expiry. cert-manager +considers certificates to be close to expiry when the 'Not After' field on the +certificate is less than the current time plus 30 days. \ No newline at end of file diff --git a/content/v1.13-docs/tutorials/acme/example/deployment.yaml b/content/v1.13-docs/tutorials/acme/example/deployment.yaml new file mode 100644 index 0000000000..d876c66cf9 --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/deployment.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kuard +spec: + selector: + matchLabels: + app: kuard + replicas: 1 + template: + metadata: + labels: + app: kuard + spec: + containers: + - image: gcr.io/kuar-demo/kuard-amd64:1 + imagePullPolicy: Always + name: kuard + ports: + - containerPort: 8080 diff --git a/content/v1.13-docs/tutorials/acme/example/ingress-tls-final.yaml b/content/v1.13-docs/tutorials/acme/example/ingress-tls-final.yaml new file mode 100644 index 0000000000..48f8094a85 --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/ingress-tls-final.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kuard + annotations: + cert-manager.io/issuer: "letsencrypt-prod" + +spec: + ingressClassName: nginx + tls: + - hosts: + - example.example.com + secretName: quickstart-example-tls + rules: + - host: example.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kuard + port: + number: 80 diff --git a/content/v1.13-docs/tutorials/acme/example/ingress-tls.yaml b/content/v1.13-docs/tutorials/acme/example/ingress-tls.yaml new file mode 100644 index 0000000000..60fef7448b --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/ingress-tls.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kuard + annotations: + cert-manager.io/issuer: "letsencrypt-staging" + +spec: + ingressClassName: nginx + tls: + - hosts: + - example.example.com + secretName: quickstart-example-tls + rules: + - host: example.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kuard + port: + number: 80 diff --git a/content/v1.13-docs/tutorials/acme/example/ingress.yaml b/content/v1.13-docs/tutorials/acme/example/ingress.yaml new file mode 100644 index 0000000000..0651471ca0 --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/ingress.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kuard + annotations: {} + #cert-manager.io/issuer: "letsencrypt-staging" +spec: + ingressClassName: nginx + tls: + - hosts: + - example.example.com + secretName: quickstart-example-tls + rules: + - host: example.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kuard + port: + number: 80 diff --git a/content/v1.13-docs/tutorials/acme/example/pomerium-certificates.yaml b/content/v1.13-docs/tutorials/acme/example/pomerium-certificates.yaml new file mode 100644 index 0000000000..7e07111417 --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/pomerium-certificates.yaml @@ -0,0 +1,36 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: pomerium-cert + namespace: pomerium +spec: + secretName: pomerium-tls + issuerRef: + name: pomerium-issuer + kind: Issuer + usages: + - server auth + - client auth + dnsNames: + - pomerium-proxy.pomerium.svc.cluster.local + - pomerium-authorize.pomerium.svc.cluster.local + - pomerium-databroker.pomerium.svc.cluster.local + - pomerium-authenticate.pomerium.svc.cluster.local +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: pomerium-redis-cert + namespace: pomerium +spec: + secretName: pomerium-redis-tls + issuerRef: + name: pomerium-issuer + kind: Issuer + usages: + - server auth + - client auth + dnsNames: + - pomerium-redis-master.pomerium.svc.cluster.local + - pomerium-redis-headless.pomerium.svc.cluster.local + - pomerium-redis-replicas.pomerium.svc.cluster.local diff --git a/content/v1.13-docs/tutorials/acme/example/pomerium-production-issuer.yaml b/content/v1.13-docs/tutorials/acme/example/pomerium-production-issuer.yaml new file mode 100644 index 0000000000..f15a289904 --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/pomerium-production-issuer.yaml @@ -0,0 +1,19 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-prod + namespace: pomerium +spec: + acme: + # The ACME server URL + server: https://acme-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-prod + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + ingressClassName: pomerium diff --git a/content/v1.13-docs/tutorials/acme/example/pomerium-staging-issuer.yaml b/content/v1.13-docs/tutorials/acme/example/pomerium-staging-issuer.yaml new file mode 100644 index 0000000000..8f8b91992a --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/pomerium-staging-issuer.yaml @@ -0,0 +1,19 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging + namespace: pomerium +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + ingressClassName: pomerium diff --git a/content/v1.13-docs/tutorials/acme/example/pomerium-values.yaml b/content/v1.13-docs/tutorials/acme/example/pomerium-values.yaml new file mode 100644 index 0000000000..2460377547 --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/pomerium-values.yaml @@ -0,0 +1,39 @@ +authenticate: + existingTLSSecret: pomerium-tls + idp: + provider: "google" + clientID: YOUR_CLIENT_ID + clientSecret: YOUR_SECRET + serviceAccount: YOUR_SERVICE_ACCOUNT + ingress: + annotations: + cert-manager.io/issuer: letsencrypt-staging + tls: + secretName: authenticate.localhost.pomerium.io-tls + +proxy: + existingTLSSecret: pomerium-tls + +databroker: + existingTLSSecret: pomerium-tls + storage: + clientTLS: + existingSecretName: pomerium-redis-tls + existingCASecretKey: ca.crt + +authorize: + existingTLSSecret: pomerium-tls + +redis: + enabled: true + generateTLS: false + tls: + certificateSecret: pomerium-redis-tls + +ingressController: + enabled: true + +config: + rootDomain: localhost.pomerium.io #Change this to your reserved domain space. + existingCASecret: pomerium-tls + generateTLS: false # On by default, disabled when cert-manager or another solution is in place. diff --git a/content/v1.13-docs/tutorials/acme/example/production-issuer.yaml b/content/v1.13-docs/tutorials/acme/example/production-issuer.yaml new file mode 100644 index 0000000000..16ed60e210 --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/production-issuer.yaml @@ -0,0 +1,18 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-prod +spec: + acme: + # The ACME server URL + server: https://acme-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-prod + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + ingressClassName: nginx diff --git a/content/v1.13-docs/tutorials/acme/example/service.yaml b/content/v1.13-docs/tutorials/acme/example/service.yaml new file mode 100644 index 0000000000..864b481ccb --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: kuard +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + selector: + app: kuard diff --git a/content/v1.13-docs/tutorials/acme/example/staging-issuer.yaml b/content/v1.13-docs/tutorials/acme/example/staging-issuer.yaml new file mode 100644 index 0000000000..9b0f337256 --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/example/staging-issuer.yaml @@ -0,0 +1,18 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + ingressClassName: nginx diff --git a/content/v1.13-docs/tutorials/acme/http-validation.md b/content/v1.13-docs/tutorials/acme/http-validation.md new file mode 100644 index 0000000000..b61bf7c14b --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/http-validation.md @@ -0,0 +1,166 @@ +--- +title: HTTP Validation +description: 'cert-manager tutorials: Issuing an ACME certificate using HTTP validation' +--- + +## Issuing an ACME certificate using HTTP validation + +cert-manager can be used to obtain certificates from a CA using the +[ACME](https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment) +protocol. The ACME protocol supports various challenge mechanisms which are +used to prove ownership of a domain so that a valid certificate can be issued +for that domain. + +One such challenge mechanism is the HTTP01 challenge. With a HTTP01 challenge, +you prove ownership of a domain by ensuring that a particular file is present at +the domain. It is assumed that you control the domain if you are able to +publish the given file under a given path. + +The following Issuer defines the necessary information to enable HTTP +validation. You can read more about the Issuer resource in the [Issuer +docs](../../concepts/issuer.md). + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging + namespace: default +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + # Enable the HTTP-01 challenge provider + solvers: + # An empty 'selector' means that this solver matches all domains + - selector: {} + http01: + ingress: + ingressClassName: nginx +``` + +We have specified the ACME server URL for Let's Encrypt's [staging +environment](https://letsencrypt.org/docs/staging-environment/). The staging +environment will not issue trusted certificates but is used to ensure that the +verification process is working properly before moving to production. Let's +Encrypt's production environment imposes much stricter [rate +limits](https://letsencrypt.org/docs/rate-limits/), so to reduce the chance of +you hitting those limits it is highly recommended to start by using the staging +environment. To move to production, simply create a new Issuer with the URL set +to `https://acme-v02.api.letsencrypt.org/directory`. + +The first stage of the ACME protocol is for the client to register with the +ACME server. This phase includes generating an asymmetric key pair which is +then associated with the email address specified in the Issuer. Make sure to +change this email address to a valid one that you own. It is commonly used to +send expiry notices when your certificates are coming up for renewal. The +generated private key is stored in a Secret named `letsencrypt-staging`. + +We must provide one or more Solvers for handling the ACME challenge. In this +case we want to use HTTP validation so we specify an `http01` Solver. We could +optionally map different domains to use different Solver configurations. + +Once we have created the above Issuer we can use it to obtain a certificate. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: default +spec: + secretName: example-com-tls + issuerRef: + name: letsencrypt-staging + commonName: example.com + dnsNames: + - www.example.com +``` + +The Certificate resource describes our desired certificate and the possible +methods that can be used to obtain it. You can learn more about the Certificate +resource in the [docs](../../concepts/certificate.md). If the certificate is +obtained successfully, the resulting key pair will be stored in a secret called +`example-com-tls` in the same namespace as the Certificate. + +The certificate will have a common name of `example.com` and the [Subject +Alternative Names +(SANs)](https://en.wikipedia.org/wiki/Subject_Alternative_Name) will be +`example.com` and `www.example.com`. Note that only these SANs will be respected +by TLS clients. + +In our Certificate we have referenced the `letsencrypt-staging` Issuer above. +The Issuer must be in the same namespace as the Certificate. If you want to +reference a `ClusterIssuer`, which is a cluster-scoped version of an Issuer, you +must add `kind: ClusterIssuer` to the `issuerRef` stanza. + +For more information on `ClusterIssuers`, read the [`ClusterIssuer` +docs](../../concepts/issuer.md). + +The `acme` stanza defines the configuration for our ACME challenges. Here we +have defined the configuration for our HTTP01 challenges which will be used to +verify domain ownership. To verify ownership of each domain mentioned in an +`http01` stanza, cert-manager will create a Pod, Service and Ingress that +exposes an HTTP endpoint that satisfies the HTTP01 challenge. + +The fields `ingressClassName`, `class`, and `name` in the `http01` stanza can be +used to control how cert-manager interacts with Ingress resources: + +- If the `ingressClassName` field is specified, a new ingress resource with a + randomly generated name will be created in order to solve the challenge. This + new resource will have the field `ingressClassName` with the value of the + `ingressClassName` field. This is the recommended way of configuring which + Ingress controller should be used. This works for the likes of the NGINX + ingress controller. +- If the `class` field is specified, a new ingress resource with a randomly + generated name will be created in order to solve the challenge. This new + resource will have an annotation with key `kubernetes.io/ingress.class` and + value set to the value of the `class` field. This field is only recommended + with ingress-gce which [does not support the `ingressClassName` + field](https://cloud.google.com/kubernetes-engine/docs/how-to/load-balance-ingress). +- If the `name` field is specified, then an Ingress resource with the same name + in the same namespace as the Certificate must already exist and it will be + modified only to add the appropriate rules to solve the challenge. This field + is useful for the Google Cloud Loadbalancer ingress controller, as well as a + number of others, that assign a single public IP address for each ingress + resource. Without manual intervention, creating a new ingress resource would + cause any challenges to fail. + +- If neither are specified, new ingress resources will be created with a randomly + generated name, but they will not have the ingress class annotation set. +- If both are specified, then the `ingress` field will take precedence. + +Once domain ownership has been verified, any cert-manager affected resources will +be cleaned up or deleted. + +> Note: It is your responsibility to point each domain name at the correct IP +> address for your ingress controller. + +After creating the above Certificate, we can check whether it has been obtained +successfully using `kubectl describe`: + +```bash +$ kubectl describe certificate example-com +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CreateOrder 57m cert-manager Created new ACME order, attempting validation... + Normal DomainVerified 55m cert-manager Domain "example.com" verified with "http-01" validation + Normal DomainVerified 55m cert-manager Domain "www.example.com" verified with "http-01" validation + Normal IssueCert 55m cert-manager Issuing certificate... + Normal CertObtained 55m cert-manager Obtained certificate from ACME server + Normal CertIssued 55m cert-manager Certificate issued successfully +``` + +You can also check whether issuance was successful with `kubectl get secret +example-com-tls -o yaml`. You should see a base64 encoded signed TLS key pair. + +Once our certificate has been obtained, cert-manager will periodically check its +validity and attempt to renew it if it gets close to expiry. cert-manager +considers certificates to be close to expiry when the 'Not After' field on the +certificate is less than the current time plus 30 days. \ No newline at end of file diff --git a/content/v1.13-docs/tutorials/acme/migrating-from-kube-lego.md b/content/v1.13-docs/tutorials/acme/migrating-from-kube-lego.md new file mode 100644 index 0000000000..84992cb0fb --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/migrating-from-kube-lego.md @@ -0,0 +1,232 @@ +--- +title: Migrating from Kube-LEGO +description: 'cert-manager tutorials: Migrating from kube-lego' +--- + +[kube-lego](https://github.com/jetstack/kube-lego) is an older Jetstack project +for obtaining TLS certificates from Let's Encrypt (or another ACME server). + +Since cert-managers release, kube-lego has been gradually deprecated in favor +of this project. There are a number of key differences between the two: + +| Feature | kube-lego | cert-manager | +|-------------------------------------------|----------------------------------|------------------------| +| Configuration | Annotations on Ingress resources | CRDs | +| CAs | ACME | ACME, signing key pair | +| Kubernetes | `v1.2` - `v1.8` | `v1.7+` | +| Debugging | Look at logs | Kubernetes Events API | +| Multi-tenancy | Not supported | Supported | +| Distinct issuance sources per Certificate | Not supported | Supported | +| Ingress controller support (ACME) | GCE, NGINX | All | + +This guide will walk through how you can safely migrate your kube-lego +installation to cert-manager, without service interruption. + +By the end of the guide, we should have: + +1. Scaled down and removed kube-lego + +2. Installed cert-manager + +3. Migrated ACME private key to cert-manager + +4. Created an ACME `ClusterIssuer` using this private key, to issue certificates + throughout your cluster + +5. Configured cert-manager's [`ingress-shim`](../../usage/ingress.md) to + automatically provision Certificate resources for all Ingress resources with + the `kubernetes.io/tls-acme: "true"` annotation, using the `ClusterIssuer` we + have created + +6. Verified that the cert-manager installation is working + + +## 1. Scale down kube-lego + +Before we begin deploying cert-manager, it is best we scale our kube-lego +deployment down to 0 replicas. This will prevent the two controllers +potentially 'fighting' each other. If you deployed kube-lego using the official +deployment YAMLs, a command like so should do: + +```bash +$ kubectl scale deployment kube-lego \ + --namespace kube-lego \ + --replicas=0 +``` + +You can then verify your kube-lego pod is no longer running with: + +```bash +$ kubectl get pods --namespace kube-lego +``` + +## 2. Deploy cert-manager + +cert-manager should be deployed using Helm, according to our official +[installation guide](../../installation/README.md). No special steps are +required here. We will return to this deployment at the end of this guide and +perform an upgrade of some of the CLI flags we deploy cert-manager with however. + +Please take extra care to ensure you have configured RBAC correctly when +deploying Helm and cert-manager - there are some nuances described in our +deploying document! + +## 3. Obtaining your ACME account private key + +In order to continue issuing and renewing certificates on your behalf, we need +to migrate the user account private key that kube-lego has created for you over +to cert-manager. + +Your ACME user account identity is a private key, stored in a secret resource. +By default, kube-lego will store this key in a secret named `kube-lego-account` +in the same namespace as your kube-lego Deployment. You may have overridden this +value when you deploy kube-lego, in which case the secret name to use will be +the value of the `LEGO_SECRET_NAME` environment variable. + +You should download a copy of this secret resource and save it in your local +directory: + +```bash +$ kubectl get secret kube-lego-account -o yaml \ + --namespace kube-lego \ + --export > kube-lego-account.yaml +``` + +Once saved, open up this file and change the `metadata.name` field to something +more relevant to cert-manager. For the rest of this guide, we'll assume you +chose `letsencrypt-private-key`. + +Once done, we need to create this new resource in the `cert-manager` namespace. +By default, cert-manager stores supporting resources for `ClusterIssuers` in the +namespace that it is running in, and we used `cert-manager` when deploying +cert-manager above. You should change this if you have deployed cert-manager +into a different namespace. + +```bash +$ kubectl create -f kube-lego-account.yaml \ + --namespace cert-manager +``` + +## 4. Creating an ACME `ClusterIssuer` using your old ACME account + +We need to create a `ClusterIssuer` which will hold information about the ACME +account previously registered via kube-lego. In order to do so, we need two more +pieces of information from our old kube-lego deployment: the server URL of the +ACME server, and the email address used to register the account. + +Both of these bits of information are stored within the kube-lego `ConfigMap`. + +To retrieve them, you should be able to `get` the `ConfigMap` using `kubectl`: + +```bash +$ kubectl get configmap kube-lego -o yaml \ + --namespace kube-lego \ + --export +``` + +Your email address should be shown under the `.data.lego.email` field, and the +ACME server URL under `.data.lego.url`. + +For the purposes of this guide, we will assume the email is +`user@example.com` and the URL +`https://acme-staging-v02.api.letsencrypt.org/directory`. + +Now that we have migrated our private key to the new Secret resource, as well as +obtaining our ACME email address and URL, we can create a `ClusterIssuer` +resource! + +Create a file named `cluster-issuer.yaml`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + # Adjust the name here accordingly + name: letsencrypt-staging +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: user@example.com + # Name of a secret used to store the ACME account private key from step 3 + privateKeySecretRef: + name: letsencrypt-private-key + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + ingressClassName: nginx +``` + +We then submit this file to our Kubernetes cluster: + +```bash +$ kubectl create -f cluster-issuer.yaml +``` + +You should be able to verify the ACME account has been verified successfully: + +```bash +$ kubectl describe clusterissuer letsencrypt-staging +... +Status: + Acme: + Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/7571319 + Conditions: + Last Transition Time: 2019-01-30T14:52:03Z + Message: The ACME account was registered with the ACME server + Reason: ACMEAccountRegistered + Status: True + Type: Ready +``` + +## 5. Configuring ingress-shim to use our new `ClusterIssuer` by default + +Now that our `ClusterIssuer` is ready to issue certificates, we have one last +thing to do: we must reconfigure `ingress-shim` (deployed as part of cert-manager) +to automatically create Certificate resources for all Ingress resources it finds +with appropriate annotations. + +More information on the role of ingress-shim can be found [in the +docs](../../usage/ingress.md), but for now we can just run a `helm +upgrade` in order to add a few additional flags. Assuming you've named your +`ClusterIssuer` `letsencrypt-staging` (as above), run: + +```bash +$ helm upgrade cert-manager \ + jetstack/cert-manager \ + --namespace cert-manager \ + --set ingressShim.defaultIssuerName=letsencrypt-staging \ + --set ingressShim.defaultIssuerKind=ClusterIssuer +``` + +You should see the cert-manager pod be re-created, and once started it should +automatically create Certificate resources for all of your ingresses that +previously had kube-lego enabled. + +## 6. Verify each ingress now has a corresponding Certificate + +Before we finish, we should make sure there is now a Certificate resource for +each ingress resource you previously enabled kube-lego on. + +You should be able to check this by running: + +```bash +$ kubectl get certificates --all-namespaces +``` + +There should be an entry for each ingress in your cluster with the kube-lego +annotation. + +We can also verify that cert-manager has 'adopted' the old TLS certificates by +viewing the logs for cert-manager: + +```bash +$ kubectl logs -n cert-manager -l app=cert-manager -c cert-manager +... +I1025 21:54:02.869269 1 sync.go:206] Certificate my-example-certificate scheduled for renewal in 292 hours +``` + +Here we can see cert-manager has verified the existing TLS certificate and +scheduled it to be renewed in 292 hours time. \ No newline at end of file diff --git a/content/v1.13-docs/tutorials/acme/nginx-ingress.md b/content/v1.13-docs/tutorials/acme/nginx-ingress.md new file mode 100644 index 0000000000..9a334f4810 --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/nginx-ingress.md @@ -0,0 +1,602 @@ +--- +title: Securing NGINX-ingress +description: 'cert-manager tutorials: Using ingress-nginx to solve an ACME HTTP-01 challenge' +--- + +This tutorial will detail how to install and secure ingress to your cluster +using NGINX. + +## Step 1 - Install Helm + +> *Skip this section if you have helm installed.* + +The easiest way to install `cert-manager` is to use [`Helm`](https://helm.sh), a +templating and deployment tool for Kubernetes resources. + +First, ensure the Helm client is installed following the [Helm installation +instructions](https://helm.sh/docs/intro/install/). + +For example, on MacOS: + +```bash +brew install kubernetes-helm +``` + +## Step 2 - Deploy the NGINX Ingress Controller + +A [`kubernetes ingress controller`](https://kubernetes.io/docs/concepts/services-networking/ingress) is +designed to be the access point for HTTP and HTTPS traffic to the software +running within your cluster. The `ingress-nginx-controller` does this by providing +an HTTP proxy service supported by your cloud provider's load balancer. + +You can get more details about `ingress-nginx` and how it works from the +[documentation for `ingress-nginx`](https://kubernetes.github.io/ingress-nginx/). + +Add the latest helm repository for the ingress-nginx + +```bash +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +``` + +Update the helm repository with the latest charts: + +```bash +$ helm repo update +Hang tight while we grab the latest from your chart repositories... +...Skip local chart repository +...Successfully got an update from the "stable" chart repository +...Successfully got an update from the "ingress-nginx" chart repository +...Successfully got an update from the "coreos" chart repository +Update Complete. ⎈ Happy Helming!⎈ +``` + +Use `helm` to install an NGINX Ingress controller: + +```bash +$ helm install quickstart ingress-nginx/ingress-nginx + +NAME: quickstart +... lots of output ... +``` + +It can take a minute or two for the cloud provider to provide and link a public +IP address. When it is complete, you can see the external IP address using the +`kubectl` command: + +```bash +$ kubectl get svc +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kubernetes ClusterIP 10.0.0.1 443/TCP 13m +quickstart-ingress-nginx-controller LoadBalancer 10.0.114.241 80:31635/TCP,443:30062/TCP 8m16s +quickstart-ingress-nginx-controller-admission ClusterIP 10.0.188.24 443/TCP 8m16s +``` + +This command shows you all the services in your cluster (in the `default` +namespace), and any external IP addresses they have. When you first create the +controller, your cloud provider won't have assigned and allocated an IP address +through the `LoadBalancer` yet. Until it does, the external IP address for the +service will be listed as ``. + +Your cloud provider may have options for reserving an IP address prior to +creating the ingress controller and using that IP address rather than assigning +an IP address from a pool. Read through the documentation from your cloud +provider on how to arrange that. + +## Step 3 - Assign a DNS name + +The external IP that is allocated to the ingress-controller is the IP to which +all incoming traffic should be routed. To enable this, add it to a DNS zone you +control, for example as `www.example.com`. + +This quick-start assumes you know how to assign a DNS entry to an IP address and +will do so. + +## Step 4 - Deploy an Example Service + +Your service may have its own chart, or you may be deploying it directly with +manifests. This quick-start uses manifests to create and expose a sample service. +The example service uses [`kuard`](https://github.com/kubernetes-up-and-running/kuard), +a demo application. + +The quick-start example uses three manifests for the sample. The first two are a +sample deployment and an associated service: + +```yaml file=./example/deployment.yaml +``` + +```yaml file=./example/service.yaml +``` + +You can create download and reference these files locally, or you can +reference them from the GitHub source repository for this documentation. +To install the example service from the tutorial files straight from GitHub, do +the following: + +```bash +kubectl apply -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/deployment.yaml +# expected output: deployment.extensions "kuard" created + +kubectl apply -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/service.yaml +# expected output: service "kuard" created +``` + +An [Ingress resource](https://kubernetes.io/docs/concepts/services-networking/ingress/) is +what Kubernetes uses to expose this example service outside the cluster. You +will need to download and modify the example manifest to reflect the domain that +you own or control to complete this example. + +A sample ingress you can start with is: + +```yaml file=./example/ingress.yaml +``` + +You can download the sample manifest from GitHub , edit it, and submit the +manifest to Kubernetes with the command below. Edit the file in your editor, and once +it is saved: + +```bash +kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/ingress.yaml +# expected output: ingress.networking.k8s.io/kuard created +``` + +> Note: The ingress example we show above has a `host` definition within it. The +> `ingress-nginx-controller` will route traffic when the hostname requested +> matches the definition in the ingress. You *can* deploy an ingress without a +> `host` definition in the rule, but that pattern isn't usable with a TLS +> certificate, which expects a fully qualified domain name. + +Once it is deployed, you can use the command `kubectl get ingress` to see the status + of the ingress: + +```text +NAME HOSTS ADDRESS PORTS AGE +kuard * 80, 443 17s +``` + +It may take a few minutes, depending on your service provider, for the ingress +to be fully created. When it has been created and linked into place, the +ingress will show an address as well: + +```text +NAME HOSTS ADDRESS PORTS AGE +kuard * 203.0.113.2 80 9m +``` + +> Note: The IP address on the ingress *may not* match the IP address that the +> `ingress-nginx-controller` has. This is fine, and is a quirk/implementation detail +> of the service provider hosting your Kubernetes cluster. Since we are using +> the `ingress-nginx-controller` instead of any cloud-provider specific ingress +> backend, use the IP address that was defined and allocated for the +> `quickstart-ingress-nginx-controller ` `LoadBalancer` resource as the primary access point for +> your service. + +Make sure the service is reachable at the domain name you added above, for +example `http://www.example.com`. The simplest way is to open a browser +and enter the name that you set up in DNS, and for which we just added the +ingress. + +You may also use a command line tool like `curl` to check the ingress. + +```bash +$ curl -kivL -H 'Host: www.example.com' 'http://203.0.113.2' +``` + +The options on this curl command will provide verbose output, following any +redirects, show the TLS headers in the output, and not error on insecure +certificates. With `ingress-nginx-controller`, the service will be available +with a TLS certificate, but it will be using a self-signed certificate +provided as a default from the `ingress-nginx-controller`. Browsers will show +a warning that this is an invalid certificate. This is expected and normal, +as we have not yet used cert-manager to get a fully trusted certificate +for our site. + +> *Warning*: It is critical to make sure that your ingress is available and +> responding correctly on the internet. This quick-start example uses Let's +> Encrypt to provide the certificates, which expects and validates both that the +> service is available and that during the process of issuing a certificate uses +> that validation as proof that the request for the domain belongs to someone +> with sufficient control over the domain. + +## Step 5 - Deploy cert-manager + +We need to install cert-manager to do the work with Kubernetes to request a +certificate and respond to the challenge to validate it. We can use Helm or +plain Kubernetes manifests to install cert-manager. + +Since we installed Helm earlier, we'll assume you want to use Helm; follow the +[Helm guide](../../installation/helm.md). For other methods, read the +[installation documentation](../../installation/README.md) for cert-manager. + +cert-manager mainly uses two different custom Kubernetes resources - known as +[`CRDs`](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) - +to configure and control how it operates, as well as to store state. These +resources are Issuers and Certificates. + +### Issuers + +An Issuer defines _how_ cert-manager will request TLS certificates. Issuers are +specific to a single namespace in Kubernetes, but there's also a `ClusterIssuer` +which is meant to be a cluster-wide version. + +Take care to ensure that your Issuers are created in the same namespace as the +certificates you want to create. You might need to add `-n my-namespace` to your +`kubectl create` commands. + +Your other option is to replace your `Issuers` with `ClusterIssuers`; +`ClusterIssuer` resources apply across all Ingress resources in your cluster. +If using a `ClusterIssuer`, remember to update the Ingress annotation `cert-manager.io/issuer` to +`cert-manager.io/cluster-issuer`. + +If you see issues with issuers, follow the [Troubleshooting Issuing ACME Certificates](../../troubleshooting/acme.md) guide. + +More information on the differences between `Issuers` and `ClusterIssuers` - including +when you might choose to use each can be found on [Issuer concepts](../../concepts/issuer.md#namespaces). + +### Certificates + +Certificates resources allow you to specify the details of the certificate you +want to request. They reference an issuer to define _how_ they'll be issued. + +For more information, see [Certificate concepts](../../concepts/certificate.md). + +## Step 6 - Configure a Let's Encrypt Issuer + +We'll set up two issuers for Let's Encrypt in this example: staging and production. + +The Let's Encrypt production issuer has [very strict rate limits](https://letsencrypt.org/docs/rate-limits/). +When you're experimenting and learning, it can be very easy to hit those limits. Because of that risk, +we'll start with the Let's Encrypt staging issuer, and once we're happy that it's working +we'll switch to the production issuer. + +Note that you'll see a warning about untrusted certificates from the staging issuer, but that's totally expected. + +Create this definition locally and update the email address to your own. This +email is required by Let's Encrypt and used to notify you of certificate +expiration and updates. + +```yaml file=./example/staging-issuer.yaml +``` + +Once edited, apply the custom resource: + +```bash +kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/staging-issuer.yaml +# expected output: issuer.cert-manager.io "letsencrypt-staging" created +``` + +Also create a production issuer and deploy it. As with the staging issuer, you +will need to update this example and add in your own email address. + +```yaml file=./example/production-issuer.yaml +``` + +```bash +kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/production-issuer.yaml +# expected output: issuer.cert-manager.io "letsencrypt-prod" created +``` + +Both of these issuers are configured to use the [`HTTP01`](../../configuration/acme/http01/README.md) challenge provider. + +Check on the status of the issuer after you create it: + +```bash +$ kubectl describe issuer letsencrypt-staging +Name: letsencrypt-staging +Namespace: default +Labels: +Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"cert-manager.io/v1","kind":"Issuer","metadata":{"annotations":{},"name":"letsencrypt-staging","namespace":"default"},(...)} +API Version: cert-manager.io/v1 +Kind: Issuer +Metadata: + Cluster Name: + Creation Timestamp: 2018-11-17T18:03:54Z + Generation: 0 + Resource Version: 9092 + Self Link: /apis/cert-manager.io/v1/namespaces/default/issuers/letsencrypt-staging + UID: 25b7ae77-ea93-11e8-82f8-42010a8a00b5 +Spec: + Acme: + Email: email@example.com + Private Key Secret Ref: + Key: + Name: letsencrypt-staging + Server: https://acme-staging-v02.api.letsencrypt.org/directory + Solvers: + Http 01: + Ingress: + Class: nginx +Status: + Acme: + Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/7374163 + Conditions: + Last Transition Time: 2018-11-17T18:04:00Z + Message: The ACME account was registered with the ACME server + Reason: ACMEAccountRegistered + Status: True + Type: Ready +Events: +``` + +You should see the issuer listed with a registered account. + +## Step 7 - Deploy a TLS Ingress Resource + +With all the prerequisite configuration in place, we can now do the pieces to +request the TLS certificate. There are two primary ways to do this: using +annotations on the ingress with [`ingress-shim`](../../usage/ingress.md) or +directly creating a certificate resource. + +In this example, we will add annotations to the ingress, and take advantage +of ingress-shim to have it create the certificate resource on our behalf. +After creating a certificate, the cert-manager will update or create a ingress +resource and use that to validate the domain. Once verified and issued, +cert-manager will create or update the secret defined in the certificate. + +> Note: The secret that is used in the ingress should match the secret defined +> in the certificate. There isn't any explicit checking, so a typo will result +> in the `ingress-nginx-controller` falling back to its self-signed certificate. +> In our example, we are using annotations on the ingress (and ingress-shim) +> which will create the correct secrets on your behalf. + +Edit the ingress add the annotations that were commented out in our earlier +example: + +```yaml file=./example/ingress-tls.yaml +``` + +and apply it: + +```bash +kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/ingress-tls.yaml +# expected output: ingress.networking.k8s.io/kuard configured +``` + +Cert-manager will read these annotations and use them to create a certificate, +which you can request and see: + +```bash +$ kubectl get certificate +NAME READY SECRET AGE +quickstart-example-tls True quickstart-example-tls 16m +``` + +cert-manager reflects the state of the process for every request in the +certificate object. You can view this information using the +`kubectl describe` command: + +```bash +$ kubectl describe certificate quickstart-example-tls +Name: quickstart-example-tls +Namespace: default +Labels: +Annotations: +API Version: cert-manager.io/v1 +Kind: Certificate +Metadata: + Cluster Name: + Creation Timestamp: 2018-11-17T17:58:37Z + Generation: 0 + Owner References: + API Version: networking.k8s.io/v1 + Block Owner Deletion: true + Controller: true + Kind: Ingress + Name: kuard + UID: a3e9f935-ea87-11e8-82f8-42010a8a00b5 + Resource Version: 9295 + Self Link: /apis/cert-manager.io/v1/namespaces/default/certificates/quickstart-example-tls + UID: 68d43400-ea92-11e8-82f8-42010a8a00b5 +Spec: + Dns Names: + www.example.com + Issuer Ref: + Kind: Issuer + Name: letsencrypt-staging + Secret Name: quickstart-example-tls +Status: + Acme: + Order: + URL: https://acme-staging-v02.api.letsencrypt.org/acme/order/7374163/13665676 + Conditions: + Last Transition Time: 2018-11-17T18:05:57Z + Message: Certificate issued successfully + Reason: CertIssued + Status: True + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CreateOrder 9m cert-manager Created new ACME order, attempting validation... + Normal DomainVerified 8m cert-manager Domain "www.example.com" verified with "http-01" validation + Normal IssueCert 8m cert-manager Issuing certificate... + Normal CertObtained 7m cert-manager Obtained certificate from ACME server + Normal CertIssued 7m cert-manager Certificate issued Successfully +``` + +The events associated with this resource and listed at the bottom +of the `describe` results show the state of the request. In the above +example the certificate was validated and issued within a couple of minutes. + +Once complete, cert-manager will have created a secret with the details of +the certificate based on the secret used in the ingress resource. You can +use the describe command as well to see some details: + +```bash +$ kubectl describe secret quickstart-example-tls +Name: quickstart-example-tls +Namespace: default +Labels: cert-manager.io/certificate-name=quickstart-example-tls +Annotations: cert-manager.io/alt-names=www.example.com + cert-manager.io/common-name=www.example.com + cert-manager.io/issuer-kind=Issuer + cert-manager.io/issuer-name=letsencrypt-staging + +Type: kubernetes.io/tls + +Data +==== +tls.crt: 3566 bytes +tls.key: 1675 bytes +``` + +Now that we have confidence that everything is configured correctly, you +can update the annotations in the ingress to specify the production issuer: + +```yaml file=./example/ingress-tls-final.yaml +``` + +```bash +$ kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/ingress-tls-final.yaml +ingress.networking.k8s.io/kuard configured +``` + +You will also need to delete the existing secret, which cert-manager is watching +and will cause it to reprocess the request with the updated issuer. + +```bash +$ kubectl delete secret quickstart-example-tls +secret "quickstart-example-tls" deleted +``` + +This will start the process to get a new certificate, and using describe +you can see the status. Once the production certificate has been updated, +you should see the example KUARD running at your domain with a signed TLS +certificate. + +```bash +$ kubectl describe certificate quickstart-example-tls +Name: quickstart-example-tls +Namespace: default +Labels: +Annotations: +API Version: cert-manager.io/v1 +Kind: Certificate +Metadata: + Cluster Name: + Creation Timestamp: 2018-11-17T18:36:48Z + Generation: 0 + Owner References: + API Version: networking.k8s.io/v1 + Block Owner Deletion: true + Controller: true + Kind: Ingress + Name: kuard + UID: a3e9f935-ea87-11e8-82f8-42010a8a00b5 + Resource Version: 283686 + Self Link: /apis/cert-manager.io/v1/namespaces/default/certificates/quickstart-example-tls + UID: bdd93b32-ea97-11e8-82f8-42010a8a00b5 +Spec: + Dns Names: + www.example.com + Issuer Ref: + Kind: Issuer + Name: letsencrypt-prod + Secret Name: quickstart-example-tls +Status: + Conditions: + Last Transition Time: 2019-01-09T13:52:05Z + Message: Certificate does not exist + Reason: NotFound + Status: False + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Generated 18s cert-manager Generated new private key + Normal OrderCreated 18s cert-manager Created Order resource "quickstart-example-tls-889745041" +``` + +You can see the current state of the ACME Order by running `kubectl describe` +on the Order resource that cert-manager has created for your Certificate: + +```bash +$ kubectl describe order quickstart-example-tls-889745041 +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Created 90s cert-manager Created Challenge resource "quickstart-example-tls-889745041-0" for domain "www.example.com" +``` + +Here, we can see that cert-manager has created 1 'Challenge' resource to fulfill +the Order. You can dig into the state of the current ACME challenge by running +`kubectl describe` on the automatically created Challenge resource: + +```bash +$ kubectl describe challenge quickstart-example-tls-889745041-0 +... +Status: + Presented: true + Processing: true + Reason: Waiting for http-01 challenge propagation + State: pending +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Started 15s cert-manager Challenge scheduled for processing + Normal Presented 14s cert-manager Presented challenge using http-01 challenge mechanism +``` + +From above, we can see that the challenge has been 'presented' and cert-manager +is waiting for the challenge record to propagate to the ingress controller. +You should keep an eye out for new events on the challenge resource, as a +'success' event should be printed after a minute or so (depending on how fast +your ingress controller is at updating rules): + +```bash +$ kubectl describe challenge quickstart-example-tls-889745041-0 +... +Status: + Presented: false + Processing: false + Reason: Successfully authorized domain + State: valid +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Started 71s cert-manager Challenge scheduled for processing + Normal Presented 70s cert-manager Presented challenge using http-01 challenge mechanism + Normal DomainVerified 2s cert-manager Domain "www.example.com" verified with "http-01" validation +``` + +> Note: If your challenges are not becoming 'valid' and remain in the 'pending' +> state (or enter into a 'failed' state), it is likely there is some kind of +> configuration error. Read the [Challenge resource reference +> docs](../../reference/api-docs.md#acme.cert-manager.io/v1.Challenge) for more +> information on debugging failing challenges. + +Once the challenge(s) have been completed, their corresponding challenge +resources will be *deleted*, and the 'Order' will be updated to reflect the +new state of the Order: + +```bash +$ kubectl describe order quickstart-example-tls-889745041 +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Created 90s cert-manager Created Challenge resource "quickstart-example-tls-889745041-0" for domain "www.example.com" + Normal OrderValid 16s cert-manager Order completed successfully +``` + +Finally, the 'Certificate' resource will be updated to reflect the state of the +issuance process. If all is well, you should be able to 'describe' the Certificate +and see something like the below: + +```bash +$ kubectl describe certificate quickstart-example-tls +Status: + Conditions: + Last Transition Time: 2019-01-09T13:57:52Z + Message: Certificate is up to date and has not expired + Reason: Ready + Status: True + Type: Ready + Not After: 2019-04-09T12:57:50Z +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Generated 11m cert-manager Generated new private key + Normal OrderCreated 11m cert-manager Created Order resource "quickstart-example-tls-889745041" + Normal OrderComplete 10m cert-manager Order "quickstart-example-tls-889745041" completed successfully +``` diff --git a/content/v1.13-docs/tutorials/acme/pomerium-ingress.md b/content/v1.13-docs/tutorials/acme/pomerium-ingress.md new file mode 100644 index 0000000000..90447f66af --- /dev/null +++ b/content/v1.13-docs/tutorials/acme/pomerium-ingress.md @@ -0,0 +1,191 @@ +--- +title: Pomerium Ingress +description: 'cert-manager tutorials: Solving ACME HTTP-01 challenges using Pomerium ingress' +--- + +This tutorial covers installing the [Pomerium Ingress Controller](https://pomerium.com/docs/k8s/ingress.html) and securing it with cert-manager. [Pomerium](https://pomerium.com) is an identity-aware proxy that can also provide a custom ingress controller for your Kubernetes services. + +## Prerequisites + +1. Install [Kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) and set the context to the cluster you'll be working with. + +1. Pomerium connects to an identity provider (**IdP**) to authenticate users. See one of their [guides](https://www.pomerium.com/docs/identity-providers/) to learn how to set up your IdP of choice to provide oauth2 validation. + +1. This tutorial assumes you have a domain space reserved for this cluster (such as `*.example.com`). You will need access to DNS for this domain to assign A and CNAME records as needed. + +## Install The Pomerium Ingress Controller + +1. Install Pomerium to your cluster: + + ```sh + kubectl apply -f https://raw.githubusercontent.com/pomerium/ingress-controller/main/deployment.yaml + ``` + + Define a Secret with your IdP configuration. See Pomerium's [Identity Providers](https://www.pomerium.com/docs/identity-providers) pages for more information specific to your IdP: + + ```yaml + apiVersion: v1 + kind: Secret + metadata: + name: idp + namespace: pomerium + type: Opaque + stringData: + client_id: ${IDP_PROVIDED_CLIENT_ID} + client_secret: ${IDP_PROVIDED_CLIENT_SECRET} + ``` + + Add the secret to the cluster with `kubectl apply -f`. + +1. Define the global settings for Pomerium: + + ```yaml + apiVersion: ingress.pomerium.io/v1 + kind: Pomerium + metadata: + name: global + namespace: pomerium + spec: + secrets: pomerium/bootstrap + authenticate: + url: https://authenticate.example.com + identityProvider: + provider: ${YOUR_IdP} + secret: pomerium/idp + # certificates: + # - pomerium/pomerium-proxy-tls + ``` + + Replace `${YOUR_IdP}` with your identity provider. Apply with `kubectl -f`. + + Note that the last two lines are commented out. They reference a TLS certificate we will create further in the process. + +## Install cert-manager + +Install cert-manager using any of the methods documented in the [Installation](https://cert-manager.io/docs/installation/) section of the cert-manager docs. The simplest method is to download and apply the provided manifest: + +```sh +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.10.1/cert-manager.yaml +``` + +## Configure Let's Encrypt Issuer + +For communication between the Ingresses and the internet, we'll want to use certificates signed by a trusted certificate authority like Let's Encrypt. This example creates two Let's Encrypt issuers, one for staging and one for production. + +The Let's Encrypt production issuer has [strict rate limits](https://letsencrypt.org/docs/rate-limits/). Before your configuration is finalized you may have to recreate services several times, hitting those limits. It's easy to confuse rate limiting with errors in configuration or operation while building your stack. + +Because of this, we will start with the Let's Encrypt staging issuer. Once your configuration is all but finalized, we will switch to a production issuer. Both of these issuers are configured to use the [`HTTP01`](../../configuration/acme/http01/README.md) challenge provider. + +1. The following YAML defines a staging certificate issuer. You must update the email address to your own. The `email` field is required by Let's Encrypt and used to notify you of certificate expiration and updates. + + ```yaml file=./example/pomerium-staging-issuer.yaml + ``` + + You can download and edit the example and apply it with `kubectl apply -f`, or edit, and apply the custom resource in one command: + + ```bash + kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/pomerium-staging-issuer.yaml + ``` + +1. Create a production issuer and deploy it. As with the staging issuer, update this example with your own email address: + + ```yaml file=./example/pomerium-production-issuer.yaml + ``` + + ```bash + kubectl create --edit -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/pomerium-production-issuer.yaml + ``` + +1. You can confirm on the status of the issuers after you create them: + + ```bash + kubectl describe issuer -n pomerium letsencrypt-staging + kubectl describe issuer -n pomerium letsencrypt-prod + ``` + + You should see the issuer listed with a registered account. + +1. Define a certificate for the Pomerium Proxy service. This should be the only certificate you need to manually define: + + ```yaml + apiVersion: cert-manager.io/v1 + kind: Certificate + metadata: + name: pomerium-proxy-tls + namespace: pomerium + spec: + dnsNames: + - 'authenticate.example.com' + issuerRef: + kind: Issuer + name: letsencrypt-staging + secretName: pomerium-proxy-tls + ``` + + Adjust the `dnsNames` value to match your domain space. The subdomain (`authenticate` in our example) must match the domain used for the callback URL in your IdP configuration. Add the certificate with `kubectl -f`. + +1. Uncomment the last two lines of the Pomerium global configuration that reference your newly created certificate, and re-apply to the cluster. + +Pomerium should now be installed and running in your cluster. You can verify by going to `https://authenticate.example.com` in your browser. Use `kubectl describe pomerium` to review the status of the Pomerium deployment and see recent events. + +## Define a Test Service + +To test our new Ingress Controller, we will add the [kuard](https://github.com/kubernetes-up-and-running/kuard) app to our cluster and define an Ingress for it. + +1. Define the kuard deployment and associated service: + + ```yaml file=./example/deployment.yaml + ``` + + ```yaml file=./example/service.yaml + ``` + + You can download and reference these files locally, or you can reference them from the GitHub source repository for this documentation. + + To install the example service from the tutorial files straight from GitHub: + + ```bash + kubectl apply -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/deployment.yaml + kubectl apply -f https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/service.yaml + ``` + +1. Create a new Ingress manifest (`example-ingress.yaml`) for our test service: + + ```yaml + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: kuard + annotations: + cert-manager.io/issuer: letsencrypt-staging + ingress.pomerium.io/policy: '[{"allow":{"and":[{"domain":{"is":"example.com"}}]}}]' + spec: + ingressClassName: pomerium + rules: + - host: kuard.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kuard + port: + number: 80 + tls: + - hosts: + - kuard.example.com + secretName: kuard.example.com-tls + ``` + + Again, change the references to `example.com` to match your domain space. + +1. Apply the Ingress manifest to the cluster: + + ```bash + kubectl apply -f example-ingress.yaml + ``` + +The Pomerium Ingress Controller will use cert-manager to automatically provision a certificate from the `letsencrypt-staging` issuer for the route to `kuard.example.com`. + +Once you've configured all your application services correctly in the cluster, adjust the issuer for your Ingresses (including the Authenticate service) to use `letsencrypt-prod`. diff --git a/content/v1.13-docs/tutorials/backup.md b/content/v1.13-docs/tutorials/backup.md new file mode 100644 index 0000000000..8a1f9f69fa --- /dev/null +++ b/content/v1.13-docs/tutorials/backup.md @@ -0,0 +1,167 @@ +--- +title: Backup and Restore Resources +description: 'cert-manager tutorials: Backing up your cert-manager installation' +--- + +If you need to uninstall cert-manager, or transfer your installation to a new +cluster, you can backup all of cert-manager's configuration in order to later +re-install. + +## Backing up cert-manager resource configuration + +The following commands will back up the configuration of `cert-manager` +resources. Doing that might be useful before upgrading `cert-manager`. As +this backup does not include the `Secrets` containing the X.509 +certificates, restoring to a cluster that does not already have those +`Secret` objects will result in the certificates being reissued. + +### Backup + +To backup all of your cert-manager configuration resources, run: + +```bash +kubectl get --all-namespaces -oyaml issuer,clusterissuer,cert > backup.yaml +``` + +If you are transferring data to a new cluster, you may also need to copy across +additional `Secret` resources that are referenced by your configured Issuers, such +as: + +#### CA Issuers + +- The root CA `Secret` referenced by `issuer.spec.ca.secretName` + +#### Vault Issuers + +- The token authentication `Secret` referenced by + `issuer.spec.vault.auth.tokenSecretRef` +- The AppRole configuration `Secret` referenced by + `issuer.spec.vault.auth.appRole.secretRef` + +#### ACME Issuers + +- The ACME account private key `Secret` referenced by `issuer.acme.privateKeySecretRef` +- Any `Secret`s referenced by DNS providers configured under the + `issuer.acme.dns01.providers` and `issuer.acme.solvers.dns01` fields. + +### Restore + +In order to restore your configuration, you can `kubectl apply` the files +created above after installing cert-manager, with the exception of the +`uid` and `resourceVersion` fields that do not need to be restored: + +```bash +kubectl apply -f <(awk '!/^ *(resourceVersion|uid): [^ ]+$/' backup.yaml) +``` + +## Full cluster backup and restore + +This section refers to backing up and restoring 'all' Kubernetes resources in a +cluster — including some `cert-manager` ones — for scenarios such as disaster +recovery, cluster migration etc. + +*Note*: We have tested this process on simple Kubernetes test clusters with a limited set of Kubernetes releases. To avoid data loss, please test both the backup and the restore strategy on your own cluster before depending upon it in production. If you encounter any errors, please open a GitHub issue or a PR to document variations on this process for different Kubernetes environments. + +### Avoiding unnecessary certificate reissuance + +#### Order of restore + +If `cert-manager` does not find a Kubernetes `Secret` with an X.509 certificate +for a `Certificate`, reissuance will be triggered. To avoid unnecessary +reissuance after a restore, ensure that `Secret`s are restored before +`Certificate`s. Similarly, `Secret`s should be restored before `Ingress`es if you +are using [`ingress-shim`](../usage/ingress.md). + +#### Excluding some cert-manager resources from backup + +`cert-manager` has a number of custom resources that are designed to represent a +point-in-time operation. An example would be a `CertificateRequest` that +represents a one-time request for an X.509 certificate. The status of these +resources can depend on other ephemeral resources (such as a temporary `Secret` +holding a private key) so `cert-manager` might not be able to correctly recreate +the state of these resources at a later point. + +In most cases backup and restore tools will not restore the statuses of custom resources, +so including such one-time resources in a backup can result in an unnecessary reissuance +after a restore as without the status fields `cert-manager` will not be able to tell that, +for example, an `Order` has already been fulfilled. +To avoid unnecessary reissuance, we recommend that `Order`s and `Challenge`s are excluded +from the backup. We also don't recommend backing up `CertificateRequest`s, see [Backing up CertificateRequests](#backing-up-certificaterequests) + +### Restoring Ingress Certificates + +A `Certificate` created for an `Ingress` via [`ingress-shim`](../usage/ingress.md) will have an [owner +reference](https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#owners-and-dependents) +pointing to the `Ingress` resource. `cert-manager` uses the owner reference to +verify that the `Certificate` 'belongs' to that `Ingress` and will not attempt to +create/correct it for an existing `Certificate`. After a full +cluster recreation, a restored owner reference would probably be incorrect +(`Ingress` UUID will have changed). The incorrect owner reference could lead +to a situation where updates to the `Ingress` (i.e a new DNS name) are not +applied to the `Certificate`. + +To avoid this issue, in most cases `Certificate`s created via `ingress-shim` +can be excluded from the backup. Given that the restore happens +in the correct order (`Secret` with the X.509 certificate restored before +the `Ingress`) `cert-manager` will be able to create a new `Certificate` +for the `Ingress` and determine that the existing `Secret` is for that `Certificate`. + +### Velero + +We have briefly tested backup and restore with `velero` `v1.5.3` and +`cert-manager` versions `v1.3.1` and `v1.3.0` as well as `velero` `v1.3.1` + and `cert-manager` `v1.1.0`. + + A few potential edge cases: + +- Ensure that the backups include `cert-manager` CRDs. + For example, we have seen that if `--exclude-namespaces` flag is passed to + `velero backup create`, CRDs for which there are no actual resources to be + included in the backup might also not be included in backup unless + `--include-cluster-resources=true` flag is also passed to the backup command. + +- Velero does not restore statuses of custom resources, so you should probably + exclude `Order`s, `Challenge`s and `CertificateRequest`s from the backup, see + [Excluding some cert-manager resources from backup](#excluding-some-cert-manager-resources-from-backup). + +- Velero's [default restore order](https://github.com/vmware-tanzu/velero/blob/main/pkg/cmd/server/server.go#L470)(`Secrets` before `Ingress`es, Custom Resources + restored last), should ensure that there is no unnecessary certificate reissuance + due to the order of restore operation, see [Order of restore](#order-of-restore). + +- When restoring the deployment of `cert-manager` itself, it may be necessary to + restore `cert-manager`'s RBAC resources before the rest of the deployment. + This is because `cert-manager`'s controller needs to be able to create + `Certificate`'s for the `cert-manager`'s webhook before the webhook can become + ready. In order to do this, the controller needs the right permissions. Since + Velero by default restores pods before RBAC resources, the restore might get + stuck waiting for the webhook pod to become ready. + +- Velero does not restore owner references, so it may be necessary to exclude + `Certificate`s created for `Ingress`es from the backup even when not + re-creating the `Ingress` itself. See [Restoring Ingress Certificates](#restoring-ingress-certificates). + +## Backing up CertificateRequests + + We no longer recommend including `CertificateRequest` resources in a backup + for most scenarios. + `CertificateRequest`s are designed to represent a one-time + request for an X.509 certificate. Once the request has been fulfilled, + `CertificateRequest` can usually be safely deleted[^1]. In most cases (such as when + a `CertificateRequest` has been created for a `Certificate`) a new + `CertificateRequest` will be created when needed (i.e at a time of a renewal + of a `Certificate`). + In `v1.3.0` , as part of our work towards [policy + implementation](https://github.com/cert-manager/cert-manager/pull/3727) we + introduced identity fields for `CertificateRequest` resources where, at a time + of creation, `cert-mananager`'s webhook updates `CertificateRequest`'s spec + with immutable identity fields, representing the identity of the creator of + the `CertificateRequest`. + This introduces some extra complexity for backing up + and restoring `CertificateRequest`s as the identity of the restorer might + differ from that of the original creator and in most cases a restored + `CertificateRequest` would likely end up with incorrect state. + + [^1]: there is an edge case where certain changes to `Certificate` spec may not + trigger re-issuance if there is no `CertificateRequest` for that + `Certificate`. See [documentation on when do certificates get + re-issued](../faq/README.md#when-do-certs-get-re-issued). \ No newline at end of file diff --git a/content/v1.13-docs/tutorials/getting-started-aks-letsencrypt/README.md b/content/v1.13-docs/tutorials/getting-started-aks-letsencrypt/README.md new file mode 100644 index 0000000000..f712fa6b7a --- /dev/null +++ b/content/v1.13-docs/tutorials/getting-started-aks-letsencrypt/README.md @@ -0,0 +1,687 @@ +--- +title: Deploy cert-manager on Azure Kubernetes Service (AKS) and use Let's Encrypt to sign a certificate for an HTTPS website +description: | + Learn how to deploy cert-manager on Azure Kubernetes Service (AKS) + and configure it to get a signed certificate from Let's Encrypt for an HTTPS web server, + using the DNS-01 protocol and Azure DNS with workload identity federation. +--- + +*Last Verified: 10 January 2023* + +In this tutorial you will learn how to deploy and configure cert-manager on Azure Kubernetes Service (AKS) +and how to deploy an HTTPS web server and make it available on the Internet. +You will learn how to configure cert-manager to get a signed certificate from Let's Encrypt, +which will allow clients to connect to your HTTPS website securely. +You will configure cert-manager to use the [Let's Encrypt DNS-01 challenge protocol](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) with Azure DNS, +using workload identity federation to authenticate to Azure. + +> **Microsoft Azure**: A suite of cloud computing services by Microsoft.
              +> **Kubernetes**: Runs on your servers. Automates the deployment, scaling, and management of containerized applications.
              +> **cert-manager**: Runs in Kubernetes. Obtains TLS / SSL certificates and ensures the certificates are valid and up-to-date.
              +> **Let’s Encrypt**: An Internet service. Allows you to generate free short-lived SSL certificates. + +# Part 1 + +In the first part of this tutorial you will learn the basics required to deploy an HTTPS website on an Azure Kubernetes cluster using cert-manager to create the SSL certificate for the web server. +You will create a DNS domain for your website, create an Azure Kubernetes cluster, install cert-manager, create an SSL certificate and then deploy a web server which responds to HTTPS requests from clients on the Internet. +But the SSL certificate in part 1 is only for testing purposes. + +In part 2 you will learn how to configure cert-manager to use Let's Encrypt and Azure DNS to create a trusted SSL certificate which you can use in production. + +## Configure the Azure CLI (`az`) + +If your have not already done so, [download and install the Azure CLI (`az`)](https://learn.microsoft.com/en-us/cli/azure/). + +Set up the `az` command for interactive use: + +```bash +az init +``` + +Log in, if you have not already done so: + +```bash +az login +``` + +Set the default resource group and location: + +```bash +export AZURE_DEFAULTS_GROUP=your-resource-group # ❗ Your Azure resource group +export AZURE_DEFAULTS_LOCATION=eastus2 # ❗ Your Azure location. +``` + +> ℹ️ You will need an `az` version `>=2.40.0`. Run `az version` to print the current version. +> +> ℹ️ When you run `az init`, choose "Optimize for interaction" when prompted. +> +> ℹ️ When you run `az login`, a web browser will be opened at https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize. +> Continue the login in the web browser and then return to your terminal. +>> +> 📖 Read the [Azure Command-Line Interface (CLI) documentation](https://learn.microsoft.com/en-us/cli/azure/). +> +> 📖 Read [CLI configuration values and environment variables](https://learn.microsoft.com/en-us/cli/azure/azure-cli-configuration?source=recommendations#cli-configuration-values-and-environment-variables) for more ways to configure the `az` defaults. + +## Create a public domain name + +In this tutorial you will deploy an HTTPS website with a publicly accessible domain name, so you will need to register a domain unless you already have one. +You could use any [domain name registrar](https://www.cloudflare.com/en-gb/learning/dns/glossary/what-is-a-domain-name-registrar/) to register a domain name for your site. +Here we will use a registrar called `Gandi` and register a cheap domain name for the purposes of this tutorial. +We will use the domain name: `cert-manager-tutorial-22.site` but you should choose your own. + +Now that you know your domain name, save it in an environment variable: + +```bash +export DOMAIN_NAME=cert-manager-tutorial-22.site # ❗ Replace this with your own DNS domain name +``` + +And add it to Azure DNS as a zone: + +```bash +az network dns zone create --name $DOMAIN_NAME +``` + +Log in to the control panel for your domain registrar and set the NS records for your domain to match the DNS names of the Azure [authoritative DNS servers](https://www.cloudflare.com/en-gb/learning/dns/dns-server-types/). +You can find these by looking for the NS records of your Azure hosted DNS zone: + +```bash +az network dns zone show --name $DOMAIN_NAME --output yaml +``` + +You can check that the NS records have been updated using `dig` to "trace" the hierarchy of NS records, +rather than using your local DNS resolver: + +```bash +dig $DOMAIN_NAME ns +trace +nodnssec +``` + +> ⏲ It **may** take more than 1 hour for the NS records to be updated in the parent zone, +> and it may take some time for the old NS records to be replaced in the caches of DNS resolver servers, +> if you looked up the DNS name before updating the NS records. +> +> 📖 Read [How do I Update My DNS Records?](https://docs.gandi.net/en/domain_names/common_operations/dns_records.html) in the `Gandi.net` docs, +> or seek the equivalent documentation for your own domain name registrar. + +## Create a Kubernetes cluster + +To get started, let's create a Kubernetes cluster in Microsoft Azure. +You will need to pick a name for your cluster. +Here, we will go with "test-cluster-1". +Save it in an environment variable: + +```bash +export CLUSTER=test-cluster-1 +``` + +Now, create the cluster using the following command: + +```bash +az aks create \ + --name ${CLUSTER} \ + --node-count 1 \ + --node-vm-size "Standard_B2s" \ + --load-balancer-sku basic +``` + +Update your `kubectl` config file with the credentials for your new cluster: + +```bash +az aks get-credentials --admin --name "$CLUSTER" +``` + +Now check that you can connect to the cluster: + +```bash +kubectl get nodes -o wide +``` + +> ⏲ It will take 4-5 minutes to create the cluster. +> +> 💵 To minimize your cloud bill, this command creates a 1-node cluster using a +> low cost virtual machine and load balancer. +> +> ⚠️ This cluster is only suitable for learning purposes it is not suitable for production use. +> +> 📖 Read [Run Kubernetes in Azure the Cheap Way](https://trstringer.com/cheap-kubernetes-in-azure/) for more cost saving tips. +> + +## Install cert-manager + +Now you can install and configure cert-manager. + +Install cert-manager using `helm` as follows: + +```bash +helm repo add jetstack https://charts.jetstack.io +helm repo update +helm upgrade cert-manager jetstack/cert-manager \ + --install \ + --create-namespace \ + --wait \ + --namespace cert-manager \ + --set installCRDs=true +``` + +This will create three Deployments and some Services and Pods in a new namespace called `cert-manager`. +It also installs various cluster scoped supporting resources such as RBAC roles and Custom Resource Definitions. + +You can view some of the resources that have been installed as follows: + +```bash +kubectl -n cert-manager get all +``` + +And you can explore the Custom Resource Definitions (cert-manager's API) using `kubectl explain`, as follows: + +```bash +kubectl explain Certificate +kubectl explain CertificateRequest +kubectl explain Issuer +``` + +> 📖 Read about [other ways to install cert-manager](../../installation/README.md). +> +> 📖 Read more about [Certificates and Issuers](../../concepts/README.md). + +## Create a test ClusterIssuer and a Certificate + +Now everything is ready for you to create your first certificate. +This will be a self-signed certificate but later we'll replace it with a Let's Encrypt signed certificate. + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/clusterissuer-selfsigned.yaml +``` +🔗 `clusterissuer-selfsigned.yaml` + +```bash +kubectl apply -f clusterissuer-selfsigned.yaml +``` + +Then use `envsubst` to substitute your chosen domain name into the following Certificate template: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/certificate.yaml +``` +🔗 `certificate.yaml` + +```bash +envsubst < certificate.yaml | kubectl apply -f - +``` + +> 🔗 If you don't already have `envsubst` installed you can [download and install a Go implementation of `envsubst`](https://github.com/a8m/envsubst). + +Use `cmctl status certificate` to check the status of the Certificate: + +```bash +cmctl status certificate www +``` + +If successful, the private key and the signed certificate will be stored in a Secret called `www-tls`. +You can use `cmctl inspect secret www-tls` to decode the base64 encoded X.509 content of the Secret: + +```terminal +$ cmctl inspect secret www-tls +... +Valid for: + DNS Names: + - www.cert-manager-tutorial-22.site + URIs: + IP Addresses: + Email Addresses: + Usages: + - digital signature + - key encipherment + - server auth +... +``` + +## Deploy a sample web server + +Now deploy a simple web server which responds to HTTPS requests with "hello world!". +The SSL / TLS key and certificate are supplied to the web server by using the `www-tls` Secret as a volume +and by mounting its contents into the file system of the `hello-app` container in the Pod: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/deployment.yaml +``` +🔗 `deployment.yaml` + +```bash +kubectl apply -f deployment.yaml +``` + +You also need to create a Kubernetes LoadBalancer Service, so that connections from the Internet can be routed to the web server Pod. +When you create the following Kubernetes Service, an Azure load balancer with an ephemeral public IP address will also be created: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/service.yaml +``` +🔗 `service.yaml` + +Create a unique DNS name for the LoadBalancer Service and then apply it: +```bash +export AZURE_LOADBALANCER_DNS_LABEL_NAME=lb-$(uuidgen) # ❗ The label must start with a lowercase ASCII letter +envsubst < service.yaml | kubectl apply -f - +``` + +Within 2-3 minutes, a load balancer should have been provisioned with a public IP. + +```bash +kubectl get service helloweb +``` + +Sample output + +```terminal +$ kubectl get service helloweb +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +helloweb LoadBalancer 10.0.141.1 20.114.151.62 443:30394/TCP 7m15s +``` + +The `EXTERNAL-IP` will be different for you and it may be different each time you re-create the LoadBalancer service, +but it will have a stable DNS host name associated with it +because you annotated the Service with `azure-dns-label-name`. +This stable DNS hostname can be used as an alias for your chosen `$DOMAIN_NAME` by creating a [DNS CNAME record](https://www.cloudflare.com/en-gb/learning/dns/dns-records/dns-cname-record/): + +```bash +az network dns record-set cname set-record \ + --zone-name $DOMAIN_NAME \ + --cname $AZURE_LOADBALANCER_DNS_LABEL_NAME.$AZURE_DEFAULTS_LOCATION.cloudapp.azure.com \ + --record-set-name www +``` + +Check that `www.$DOMAIN_NAME` now resolves to the ephemeral public IP address of the load balancer: + +```terminal +$ dig www.$DOMAIN_NAME A +... +;; QUESTION SECTION: +;www.cert-manager-tutorial-22.site. IN A +... +;; ANSWER SECTION: +www.cert-manager-tutorial-22.site. 3600 IN CNAME lb-ec8776e1-d067-4d4c-8cce-fdf07ce48260.eastus2.cloudapp.azure.com. +lb-ec8776e1-d067-4d4c-8cce-fdf07ce48260.eastus2.cloudapp.azure.com. 10 IN A 20.122.27.189 +... +``` + +If the DNS is correct and the load balancer is working and the hello world web server is running, +you should now be able to connect to it using curl or using your web browser: + +```bash +curl --insecure -v https://www.$DOMAIN_NAME +``` + +> ⚠️ We used curl's `--insecure` option because it rejects self-signed certificates by default. +> Later you will learn how to create a trusted certificate signed by Let's Encrypt. + +You should see that the certificate has the expected DNS names and that it is self-signed: + +```terminal +... +* Server certificate: +* subject: CN=www.cert-manager-tutorial-22.site +* start date: Jan 4 15:28:30 2023 GMT +* expire date: Apr 4 15:28:30 2023 GMT +* issuer: CN=www.cert-manager-tutorial-22.site +* SSL certificate verify result: self-signed certificate (18), continuing anyway. +... +Hello, world! +Protocol: HTTP/2.0! +Hostname: helloweb-55cb4cd887-tjlvh +``` + +> 📖 Read more about [Using a Service to Expose Your App](https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/). +> +> 📖 Read more about [Using a public IP address and DNS label with the Azure Kubernetes Service (AKS) load balancer](https://learn.microsoft.com/en-us/azure/aks/static-ip). + +# Part 2 + +In part 1 you created a test certificate. +Now you will learn how to configure cert-manager to use Let's Encrypt and Azure DNS to create a trusted certificate which you can use in production. +You need to prove to Let's Encrypt that you own the domain name of the certificate and one way to do this is to create a special DNS record in that domain. +This is known as the [DNS-01 challenge type](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge). + +cert-manager can create that DNS record for you in by using the Azure DNS API but it needs to authenticate to Azure first, +and currently the most secure method of authentication is to use [workload identity federation](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview). +The advantages of this method are that cert-manager will use an ephemeral Kubernetes ServiceAccount Token to authenticate to Azure and the token need not be stored in a Kubernetes Secret. + +> ℹ️ cert-manager `>= v1.11.0` supports workload identity federation for ACME (Let's Encrypt) DNS-01 with Azure DNS. +> Older versions of cert-manager support other authentication mechanisms which are not covered in this tutorial. +> +> 📖 Read about [other ways to configure the ACME issuer with Azure DNS](../../configuration/acme/dns01/azuredns.md). + +## Install the Azure workload identity features + +The workload identity features in Azure AKS are relatively new (at time of writing) and they require some non-default features to be enabled. + +Install the [Azure CLI AKS Preview Extension](https://github.com/Azure/azure-cli-extensions/tree/main/src/aks-preview), +which you will need to configure some advanced workload identity federation features on your AKS cluster. + +```bash +az extension add --name aks-preview +``` + +Register the `EnableWorkloadIdentityPreview` feature flag which is required for the AKS cluster in this demo. + +```bash +az feature register --namespace "Microsoft.ContainerService" --name "EnableWorkloadIdentityPreview" + +# It takes a few minutes for the status to show Registered. Verify the registration status by using the az feature list command: +az feature list -o table --query "[?contains(name, 'Microsoft.ContainerService/EnableWorkloadIdentityPreview')].{Name:name,State:properties.state}" + +# When ready, refresh the registration of the Microsoft.ContainerService resource provider by using the az provider register command: +az provider register --namespace Microsoft.ContainerService +``` + +> 📖 Read more about [Registering the `EnableWorkloadIdentityPreview` feature flag](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster). + +## Reconfigure the cluster + +Next enable the workload identity federation features on the cluster that you created earlier: + +```bash +az aks update \ + --name ${CLUSTER} \ + --enable-oidc-issuer \ + --enable-workload-identity # ℹ️ This option is currently only available when using the aks-preview extension. +``` + +> 📖 Read [Deploy and configure workload identity on an Azure Kubernetes Service (AKS) cluster](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster) for more information about the `--enable-workload-identity` feature. + +## Reconfigure cert-manager + +We will label the cert-manager controller Pod and ServiceAccount for the attention of the Azure Workload Identity webhook, +which will result in the cert-manager controller Pod having an extra volume containing a Kubernetes ServiceAccount token which it will use to authenticate with Azure. + +The labels can be configured using the Helm values file below: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/values.yaml +``` +🔗 `values.yaml` + +```bash +helm upgrade cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --reuse-values \ + --values values.yaml +``` + +The newly rolled out cert-manager Pod will have some new environment variables set, +and the Azure workload-identity ServiceAccount token as a projected volume: + +```bash +kubectl describe pod -n cert-manager -l app.kubernetes.io/component=controller +``` + +```terminal +Containers: + ... + cert-manager-controller: + ... + Environment: + ... + AZURE_CLIENT_ID: + AZURE_TENANT_ID: f99bd6a4-665c-41cf-aff1-87a89d5c62d4 + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + AZURE_AUTHORITY_HOST: https://login.microsoftonline.com/ + Mounts: + /var/run/secrets/azure/tokens from azure-identity-token (ro) +Volumes: + ... + azure-identity-token: + Type: Projected (a volume that contains injected data from multiple sources) + TokenExpirationSeconds: 3600 +``` + +> 📖 Read about [the role of the Mutating Admission Webhook](https://azure.github.io/azure-workload-identity/docs/installation/mutating-admission-webhook.html) in Azure AD Workload Identity for Kubernetes. +> +> 📖 Read about [other values that can be customized in the cert-manager Helm chart](https://artifacthub.io/packages/helm/cert-manager/cert-manager?modal=values). + +## Create an Azure Managed Identity + +When cert-manager creates a certificate using Let's Encrypt +it can use DNS records to prove that it controls the DNS domain names in the certificate. +In order for cert-manager to use the Azure API and manipulate the records in the Azure DNS zone, +it needs an Azure account and the best type of account to use is called a "Managed Identity". +This account does not come with a password or an API key and it is designed for use by machines rather than humans. + +Choose a managed identity name: + +```bash +export USER_ASSIGNED_IDENTITY_NAME=cert-manager-tutorials-1 # ❗ Replace with your preferred managed identity name +``` + +Create the Managed Identity: + +```bash +az identity create --name "${USER_ASSIGNED_IDENTITY_NAME}" +``` + +Grant it permission to modify the DNS zone records: + +```bash +export USER_ASSIGNED_IDENTITY_CLIENT_ID=$(az identity show --name "${USER_ASSIGNED_IDENTITY_NAME}" --query 'clientId' -o tsv) +az role assignment create \ + --role "DNS Zone Contributor" \ + --assignee $USER_ASSIGNED_IDENTITY_CLIENT_ID \ + --scope $(az network dns zone show --name $DOMAIN_NAME -o tsv --query id) +``` + +> 📖 Read [What are managed identities for Azure resources?](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) +> for an overview of managed identities and their uses. +> +> 📖 Read [Azure built-in roles](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) to learn about the "DNS Zone Contributor" role. + +## Add a federated identity + +Now we will configure Azure to trust certain Kubernetes ServiceAccount tokens, +in particular, the service account tokens from our specific Kubernetes cluster, +and only tokens which are associated with the cert-manager ServiceAccount. +cert-manager will authenticate to Azure using an short lived Kubernetes ServiceAccount token, +and it will be able to impersonate the managed identity that you created in the previous step. + +First export the following environment variables containing the name and namespace of the Kubernetes ServiceAccount used by the cert-manager controller: + +```bash +export SERVICE_ACCOUNT_NAME=cert-manager # ℹ️ This is the default Kubernetes ServiceAccount used by the cert-manager controller. +export SERVICE_ACCOUNT_NAMESPACE=cert-manager # ℹ️ This is the default namespace for cert-manager. +``` + +Then configure the managed identity to trust the cert-manager Kubernetes ServiceAccount, +by supplying its "subject" (the distinguishing name of the Kubernetes ServiceAccount) +and its "issuer" (a URL at which the JWT signing certificate and other metadata can be downloaded): + +```bash +export SERVICE_ACCOUNT_ISSUER=$(az aks show --resource-group $AZURE_DEFAULTS_GROUP --name $CLUSTER --query "oidcIssuerProfile.issuerUrl" -o tsv) +az identity federated-credential create \ + --name "cert-manager" \ + --identity-name "${USER_ASSIGNED_IDENTITY_NAME}" \ + --issuer "${SERVICE_ACCOUNT_ISSUER}" \ + --subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}" +``` + +> 📖 Read about [Workload identity federation](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation) in the Microsoft identity platform documentation. + + +## Create a ClusterIssuer for Let's Encrypt Staging + +A ClusterIssuer is a custom resource which tells cert-manager how to sign a Certificate. +In this case the ClusterIssuer will be configured to connect to the Let's Encrypt staging server, +which allows us to test everything without using up our Let's Encrypt certificate quota for the domain name. + +Save the following content to a file called `clusterissuer-lets-encrypt-staging.yaml`, change the `email` field to use your email address and apply it: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/clusterissuer-lets-encrypt-staging.yaml +``` +🔗 `clusterissuer-lets-encrypt-staging.yaml` + + +As you can see there are some variables in the `clusterissuer-lets-encrypt-staging.yaml` which need to be filled in before we apply it; +most have been defined earlier in this tutorial but you need to set the following: + +```bash +export EMAIL_ADDRESS= # ❗ Replace this with your email address +export AZURE_SUBSCRIPTION= # ❗ Replace this with your Azure account name +``` + +Now use `envsubst` to fill in the variables and pipe it into `kubectl apply`, as follows: + +```bash +export AZURE_SUBSCRIPTION_ID=$(az account show --name $AZURE_SUBSCRIPTION --query 'id' -o tsv) +envsubst < clusterissuer-lets-encrypt-staging.yaml | kubectl apply -f - +``` + +You can check the status of the ClusterIssuer: + +```bash +kubectl describe clusterissuer letsencrypt-staging +``` + +Example output + +```console +Status: + Acme: + Last Registered Email: firstname.lastname@example.com + Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/77882854 + Conditions: + Last Transition Time: 2022-11-29T13:05:33Z + Message: The ACME account was registered with the ACME server + Observed Generation: 1 + Reason: ACMEAccountRegistered + Status: True + Type: Ready +``` + +> ℹ️ Let's Encrypt uses the Automatic Certificate Management Environment (ACME) protocol +> which is why the configuration above is under a key called `acme`. +> +> ℹ️ The email address is only used by Let's Encrypt to remind you to renew the certificate after 30 days before expiry. You will only receive this email if something goes wrong when renewing the certificate with cert-manager. +> +> ℹ️ The Let's Encrypt production issuer has [very strict rate limits](https://letsencrypt.org/docs/rate-limits/). +> When you're experimenting and learning, it can be very easy to hit those limits. Because of that risk, +> we'll start with the Let's Encrypt staging issuer, and once we're happy that it's working +> we'll switch to the production issuer. +> +> 📖 Read more about [configuring the ACME Issuer](../../configuration/acme/README.md). +> + + +## Re-issue the Certificate using Let's Encrypt + +Patch the Certificate to use the staging ClusterIssuer: + +```bash +kubectl patch certificate www --type merge -p '{"spec":{"issuerRef":{"name":"letsencrypt-staging"}}}' +``` + +That should trigger cert-manager to renew the certificate: +Use `cmctl` to check: + +```bash +cmctl status certificate www +cmctl inspect secret www-tls +``` + +And finally, when the new certificate has been issued, you must restart the web server to use it: + +```bash +kubectl rollout restart deployment helloweb +``` + +You should once again be able to connect to the website, but this time you will see the Let's Encrypt staging certificate: + +```terminal +$ curl -v --insecure https://www.$DOMAIN_NAME +... +* Server certificate: +* subject: CN=www.cert-manager-tutorial-22.site +* start date: Jan 5 12:41:14 2023 GMT +* expire date: Apr 5 12:41:13 2023 GMT +* issuer: C=US; O=(STAGING) Let's Encrypt; CN=(STAGING) Artificial Apricot R3 +* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. +... +Hello, world! +Protocol: HTTP/2.0! +Hostname: helloweb-9b8bcdd56-6rxm8 +``` + +> ⚠️ We used curl's `--insecure` option again here because the Let's Encrypt staging issuer creates untrusted certificates. +> Next you will learn how to create a trusted certificate signed by the Let's Encrypt production issuer. + +## Create a production ready certificate + +Now that everything is working with the Let's Encrypt staging server, we can switch to the production server and get a trusted certificate. + +Create a Let's Encrypt production Issuer by copying the staging ClusterIssuer YAML and modifying the server URL and the names, +then apply it: + +```yaml file=../../../../public/docs/tutorials/getting-started-aks-letsencrypt/clusterissuer-lets-encrypt-production.yaml +``` +🔗 `clusterissuer-lets-encrypt-production.yaml` + + +```bash +envsubst < clusterissuer-lets-encrypt-production.yaml | kubectl apply -f - +``` + +Check the status of the ClusterIssuer: + +```bash +kubectl describe clusterissuer letsencrypt-production +``` + +Patch the Certificate to use the production ClusterIssuer: + +```bash +kubectl patch certificate www --type merge -p '{"spec":{"issuerRef":{"name":"letsencrypt-production"}}}' +``` + +That should trigger cert-manager to renew the certificate: +Use `cmctl` to check: + +```bash +cmctl status certificate www +cmctl inspect secret www-tls +``` + +And finally, when the new certificate has been issued, you must restart the web server to use it: + +```bash +kubectl rollout restart deployment helloweb +``` + +Now you should be able to connect to the web server securely, without the `--insecure` flag, +and if you visit the site in your web browser, it should show a padlock (🔒) symbol next to the URL. + +```bash +curl -v https://www.$DOMAIN_NAME +``` + +```terminal +... +* Server certificate: +* subject: CN=cert-manager-tutorial-22.site +* start date: Nov 30 15:41:40 2022 GMT +* expire date: Feb 28 15:41:39 2023 GMT +* subjectAltName: host "www.cert-manager-tutorial-22.site" matched cert's "www.cert-manager-tutorial-22.site" +* issuer: C=US; O=Let's Encrypt; CN=R3 +* SSL certificate verify ok. +... +``` + +That concludes this tutorial. +You have learned how to deploy cert-manager on Azure AKS and how to configure it to issue Let's Encrypt signed certificates using the DNS-01 protocol with Azure DNS. +You have learned about workload identity federation in Azure and learned how to configure cert-manager to authenticate to Azure using a Kubernetes ServiceAccount Token. + +## Cleanup + +After completing the tutorial you can clean up by deleting the cluster, the domain name and the managed identity, as follows: + +``` +az aks delete --name $CLUSTER +az network dns zone delete --name $DOMAIN_NAME +az identity delete --name $USER_ASSIGNED_IDENTITY_NAME +``` + +## Next Steps + +> 📖 Read other [cert-manager tutorials](../README.md) and [getting started guides](../../getting-started/README.md). +> +> 📖 Read more about [configuring the cert-manager ACME issuer with Azure DNS](../../configuration/acme/dns01/azuredns.md). diff --git a/content/v1.13-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md b/content/v1.13-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md new file mode 100644 index 0000000000..80f233aee7 --- /dev/null +++ b/content/v1.13-docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md @@ -0,0 +1,779 @@ +--- +title: Deploy cert-manager on Google Kubernetes Engine (GKE) and create SSL certificates for Ingress using Let's Encrypt +description: Learn how to deploy cert-manager on Google Kubernetes (GKE) Engine and then configure it to sign SSL certificates using Let's Encrypt +--- + +*Last Verified: 15 July 2022* + +In this tutorial you will learn how to deploy and configure cert-manager on Google Kubernetes Engine (GKE). +You will learn how to configure cert-manager to get a signed SSL certificate from Let's Encrypt, +using an [HTTP-01 challenge](https://letsencrypt.org/docs/challenge-types/#http-01-challenge). +Finally you will learn how the certificate can be used to serve an HTTPS website with a public domain name. + +> **Google Cloud**: A suite of cloud computing services by Google.
              +> **Kubernetes**: Runs on your servers. Automates the deployment, scaling, and management of containerized applications.
              +> **cert-manager**: Runs in Kubernetes. Obtains TLS / SSL certificates and ensures the certificates are valid and up-to-date.
              +> **Let’s Encrypt**: An Internet service. Allows you to generate free short-lived SSL certificates. + +First you will create a Kubernetes (GKE) cluster and deploy a sample web server. +You will then create a public IP address and a public domain name for your website. +You'll set up Ingress and Google Cloud load balancers so that Internet clients can connect to the web server using HTTP. +Finally you will use cert-manager to get an SSL certificate from Let's Encrypt +and configure the load balancer to use that certificate. +By the end of this tutorial you will be able to connect to your website from the Internet using an `https://` URL. + +## Prerequisites + +**💻 Google Cloud account** + +You will need a Google Cloud account. +Registration requires a credit card or bank account details. +Visit the [Get started with Google Cloud](https://cloud.google.com/docs/get-started) page and follow the instructions. + +> 💵 If you have never used Google Cloud before, you may be eligible for the +> [Google Cloud Free +> Program](https://cloud.google.com/free/docs/gcp-free-tier/#free-trial), which +> gives you a 90 day trial period that includes $300 in free Cloud Billing +> credits to explore and evaluate Google Cloud. + +**💻 Domain Name** + +You will need a domain name and the ability to create DNS records in that domain. We will be getting a $12 domain name from Google Domains. Google Domains is one of the many possible "domain name registrars". NameCheap and GoDaddy are two other well-known registrars. + +> 💵 If you prefer not purchasing a domain name, it is also possible to adapt this tutorial to use the IP address to serve your website and for the SSL certificate. + +**💻 Software** + + +You will also need to install the following software on your laptop: + +1. [gcloud](https://cloud.google.com/sdk/docs/install): A set of tools to create and manage Google Cloud resources. +2. [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl): The Kubernetes command-line tool which allows you to configure Kubernetes clusters. +3. [curl](https://everything.curl.dev/get): A command-line tool for connecting to a web server using HTTP and HTTPS. + +> ℹ️ Try running `gcloud components install kubectl` to quickly install `kubectl`. + +## 0. Configure `gcloud` with a Google Cloud project + +If you don't have a Google Cloud account, the command below will create one for you: + +```bash +gcloud init +``` + +You will need to answer "yes" to the following question: + +```text +Do you want to configure a default Compute Region and Zone? (Y/n)? Y +``` + +After running the command, you will shown the project name, default region, and default zone. + +Example output: + +```text +* Commands that require authentication will use firstname.lastname@example.com by default +* Commands will reference project `your-project` by default +* Compute Engine commands will use region `europe-west1` by default +* Compute Engine commands will use zone `europe-west1-b` by default +``` + +In this tutorial, we will refer to the name of the project that was selected while running `gcloud init` with the variable `PROJECT`. Where ever you see `$PROJECT` in a command, you need to either (1) replace the variable manually before you execute the command, +or (2) export the variable in your shell session. This applies to all environment variables that you will encounter in the commands listed in this tutorial. + +We will go with option (2), so we need to export the environment variables before continuing using the information that was printed by `gcloud init`: + +```bash +export PROJECT=your-project # Your Google Cloud project ID. +export REGION=europe-west1 # Your Google Cloud region. +``` + +## 1. Create a Kubernetes Cluster + +To get started, let's create a Kubernetes cluster in Google Cloud. You will need to pick a name for your cluster. Here, we will go with "test-cluster-1". Let us save it in an environment variable: + +```bash +export CLUSTER=test-cluster-1 +``` + +Now, create the cluster using the following command: + +```bash +gcloud container clusters create $CLUSTER --preemptible --num-nodes=1 +``` + +Set up the [Google Kubernetes Engine auth plugin for kubectl](https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke): + +```bash +gcloud components install gke-gcloud-auth-plugin +export USE_GKE_GCLOUD_AUTH_PLUGIN=True +gcloud container clusters get-credentials $CLUSTER +``` + +Now check that you can connect to the cluster: + +```bash +kubectl get nodes -o wide +``` + +> ⏲ It will take 4-5 minutes to create the cluster. +> +> 💵 To minimize your cloud bill, this command creates a 1-node cluster using a +> [preemptible virtual +> machine](https://cloud.google.com/kubernetes-engine/docs/how-to/preemptible-vms) +> which is cheaper than a normal virtual machine. + +## 2. Deploy a sample web server + +We will deploy a very simple web server which responds to HTTP requests with "hello world!". + +```bash +kubectl create deployment web --image=gcr.io/google-samples/hello-app:1.0 +``` + +We also need to create a Kubernetes Service, so that connections can be routed to the web server Pods: + +```bash +kubectl expose deployment web --port=8080 +``` + +> ℹ️ These [kubectl imperative commands](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/imperative-command/) are used for readability and brevity. +> Feel free to use YAML manifests and `kubectl apply -f` instead. +> +> ℹ️ The Service created by `kubectl expose` will be of type `ClusterIP` (the default) and this is only reachable by components within the cluster. Later we will create an Ingress which is how we make the service available to clients outside the cluster. +> +> 🔰 Read more about [Using a Service to Expose Your App](https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/). + +## 3. Create a static external IP address + +This tutorial is about creating a public facing HTTPS website with a Let's Encrypt SSL certificate using the HTTP01 challenge mechanism, +so we need a public IP address so that both Let's Encrypt and other Internet clients can connect to your website. + +It is easy to create a public IP address in Google Cloud and later we will associate it with your website domain name and with a Google Cloud load balancer, which will accept HTTP(S) connections from Internet clients and proxy the requests to the web servers running in your cluster. + +Create a global static IP address as follows: + +```bash +gcloud compute addresses create web-ip --global +``` + +You should see the new IP address listed: + +```bash +gcloud compute addresses list +``` + +> ⚠️ You MUST create a `global` IP address because that is a prerequisite of the [External HTTP(S) Load Balancer](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress-xlb) which we will be using in this tutorial. +> +> 💵 Global static IP addresses are only available in the Premium network service tier and are more expensive than ephemeral and standard public IP addresses. +> +> 🔰 Read more about [Network service tiers in Google Cloud](https://cloud.google.com/network-tiers). +> +> 🔰 Read more about [Reserving a static external IP address in Google Cloud](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address). + +Finally, we will save the IP address into an environment variable for later use. Display the IP address with the following command: + +```bash +gcloud compute addresses describe web-ip --format='value(address)' --global +``` + +Then, copy the output and save it into an environment variable: + +```bash +export IP_ADDRESS=198.51.100.1 # Replace with your IP address +``` + +## 4. Create a domain name for your website + +You will need a domain name for your website and Let's Encrypt checks your domain before it signs your SSL certificate, +so the domain name needs to be reachable from the Internet. + +We will purchase a cheap domain name using a credit card. Go to https://domains.google.com, and type something in the search box. For the example, we searched for `hello-app.com` because the example container that we will be deploying is called `hello-app`. Most importantly, we make sure to sort the domain names by price: + +![](/images/getting-started/screenshot_google-domains_get-a-new-domain.png) + +We don't pick `hello-app.com` because it costs $2,800; instead, we go with the one at the top: `heyapp.net`. It looks good! We then click the cart button. On the next screen, you will want to disable the auto-renewal, since we don't want to pay for this domain every year: + +![](/images/getting-started/screenshot_google-domains_your-cart.png) + +Now that you know your domain name, save it in an environment variable: + +```bash +export DOMAIN_NAME=heyapp.net +``` + +Next, you will need to create a new `A` record pointing at the IP address that we created above. Head back to https://domains.google.com/registrar, open your domain (here, `heyapp.net`) and click "DNS" on the left menu. You will see "Custom records". You want to add a new record of type `A` and put the IP address from the previous step into "data". You must leave "Host name" empty because we are configuring the top-level domain name: + +![](/images/getting-started/screenshot_google-domains_resource-records.png) + +> 🔰 Learn more about [What is a DNS A record? from the Cloudflare DNS tutorial](https://www.cloudflare.com/learning/dns/dns-records/dns-a-record/). + +> ℹ️ It is not strictly necessary to create a domain name for your website. You can connect to it using the IP address and later you can create an SSL certificate for the IP address instead of a domain name. If for some reason you can't create a domain name, then feel free to skip this section and adapt the instructions below to use an IP address instead. +> +> ℹ️ Every Google Cloud address has an automatically generated reverse DNS name like `51.159.120.34.bc.googleusercontent.com`, +> but the parent domain `googleusercontent.com` has a CAA record which prevents +> Let's Encrypt from signing certificates for the sub-domains. +> See [Certificate Authority Authorization (CAA)](https://letsencrypt.org/docs/caa/) in the Let's Encrypt documentation. + +## 5. Create an Ingress + +You won't be able to reach your website yet. +Your web server is running inside your Kubernetes cluster but there is no route or proxy through which Internet clients can connect to it, yet! +Now we will create a Kubernetes Ingress object and in Google Cloud this will trigger the creation of a various services which together allow Internet clients to reach your web server running inside your Kubernetes cluster. + +Initially we are going to create an HTTP (not an HTTPS) Ingress so that we can test the basic connectivity before adding the SSL layer. + +Copy the following YAML into a file called `ingress.yaml` and apply it: + +```yaml +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: web-ingress + annotations: + # This tells Google Cloud to create an External Load Balancer to realize this Ingress + kubernetes.io/ingress.class: gce + # This enables HTTP connections from Internet clients + kubernetes.io/ingress.allow-http: "true" + # This tells Google Cloud to associate the External Load Balancer with the static IP which we created earlier + kubernetes.io/ingress.global-static-ip-name: web-ip +spec: + defaultBackend: + service: + name: web + port: + number: 8080 +``` + +```bash +kubectl apply -f ingress.yaml +``` + +This will trigger the creation of a Google HTTP(S) loadbalancer associated with the IP address that you created earlier. +You can watch the progress and the resources that are being created: + +```bash +kubectl describe ingress web-ingress +``` + +Within 4-5 minutes all the load balancer components should be ready and you should be able to connect to the DNS name and see the response from the hello-world web server that we deployed earlier: + +``` +curl http://$DOMAIN_NAME +``` + +Example output: + +```console +Hello, world! +Version: 1.0.0 +Hostname: web-79d88c97d6-t8hj2 +``` + +At this point we have a Google load balancer which is forwarding HTTP traffic to the hello-world web server running in a Pod in our cluster. + +> ⏲ It may take 4-5 minutes for the load balancer components to be created and +> configured and for Internet clients to be routed to your web server. +> Refer to the [Troubleshooting](#troubleshooting) section if it takes longer. +> +> 🔰 Read about how to [Use a static IP addresses for HTTP(S) load balancers via Ingress annotation](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress-xlb#static_ip_addresses_for_https_load_balancers). +> +> 🔰 Read a [Summary of external Ingress annotations for GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/load-balance-ingress#summary_of_external_ingress_annotations). +> +> 🔰 Read about [Troubleshooting Ingress with External HTTP(S) Load Balancing on GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/load-balance-ingress#testing_the). +> +> ℹ️ There are two Ingress classes available for GKE Ingress. The `gce` class deploys an external load balancer and the `gce-internal` class deploys an internal load balancer. Ingress resources without a class specified default to `gce`. +> +> ⚠️ Contrary to the Kubernetes Ingress documentation, you MUST use the `kubernetes.io/ingress.class` annotation rather than the `Ingress.Spec.IngressClassName` field. +> See [ingress-gce #1301](https://github.com/kubernetes/ingress-gce/issues/1301#issuecomment-1133356812) and [ingress-gce #1337](https://github.com/kubernetes/ingress-gce/pull/1337). + + +## 6. Install cert-manager + +So finally we are ready to start creating an SSL certificate for our website. +The first thing you need to do is install cert-manager, and we'll install it the easy using `kubectl` as follows: + +``` +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.2/cert-manager.yaml +``` + +This will create three Deployments, and a bunch of Services and Pods in a new namespace called `cert-manager`. +It also installs various cluster scoped supporting resources such as RBAC roles and Custom Resource Definitions. + +You can view some of the resources that have been installed as follows: + +```bash +kubectl -n cert-manager get all +``` + +And you can explore the Custom Resource Definitions (cert-manager's API) using `kubectl explain`, as follows: + +```bash +kubectl explain Certificate +kubectl explain CertificateRequest +kubectl explain Issuer +``` + +> 🔰 Read about [other ways to install cert-manager](../../installation). +> +> 🔰 Read more about [Certificates and Issuers](../../concepts). + +## 7. Create an Issuer for Let's Encrypt Staging + +An Issuer is a custom resource which tells cert-manager how to sign a Certificate. +In this case the Issuer will be configured to connect to the Let's Encrypt staging server, +which allows us to test everything without using up our Let's Encrypt certificate quota for the domain name. + +> ℹ️ Let's Encrypt uses the Automatic Certificate Management Environment (ACME) protocol +> which is why the configuration below is under a key called `acme`. + +Save the following content to a file called `issuer-lets-encrypt-staging.yaml`, change the `email` field to use your email address and apply it: + +```yaml +# issuer-lets-encrypt-staging.yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-staging +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: # ❗ Replace this with your email address + privateKeySecretRef: + name: letsencrypt-staging + solvers: + - http01: + ingress: + name: web-ingress +``` + +```bash +kubectl apply -f issuer-lets-encrypt-staging.yaml +``` + +> ℹ️ The email address is only used by Let's Encrypt to remind you to renew the certificate after 30 days before expiry. You will only receive this email if something goes wrong when renewing the certificate with cert-manager. + +You can check the status of the Issuer: + +```bash +kubectl describe issuers.cert-manager.io letsencrypt-staging +``` + +Example output + +```console +Status: + Acme: + Last Registered Email: firstname.lastname@example.com + Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/60706744 + Conditions: + Last Transition Time: 2022-07-13T16:13:25Z + Message: The ACME account was registered with the ACME server + Observed Generation: 1 + Reason: ACMEAccountRegistered + Status: True + Type: Ready +``` + +> ℹ️ The Let's Encrypt production issuer has [very strict rate limits](https://letsencrypt.org/docs/rate-limits/). +> When you're experimenting and learning, it can be very easy to hit those limits. Because of that risk, +> we'll start with the Let's Encrypt staging issuer, and once we're happy that it's working +> we'll switch to the production issuer. +> +> ⚠️ In the next step you will see a warning about untrusted certificates because +> we start with the staging issuer, but that's totally expected. +> +> 🔰 Read more about [configuring the ACME Issuer](../../configuration/acme). + +## 8. Re-configure the Ingress for SSL + +Earlier we created an Ingress and saw that we could connect to our web server using HTTP. +Now we will reconfigure that Ingress for HTTPS. + +First a quick hack, to work around a problem with the Google Cloud ingress controller. +Create an empty Secret for your SSL certificate **before reconfiguring the Ingress** and apply it: + +```yaml +# secret.yaml +apiVersion: v1 +kind: Secret +metadata: + name: web-ssl +type: kubernetes.io/tls +stringData: + tls.key: "" + tls.crt: "" +``` + +```bash +kubectl apply -f secret.yaml +``` + +> ℹ️ This is a work around for a chicken-and-egg problem, where the ingress-gce +> controller won't update its forwarding rules unless it can first find the +> Secret that will eventually contain the SSL certificate. But Let's Encrypt +> won't sign the SSL certificate until it can get the special +> `.../.well-known/acme-challenge/...` URL which cert-manager adds to the +> Ingress and which must then be translated into Google Cloud forwarding rules, +> by the ingress-gce controller. +> +> 🔰 Read more about [Kubernetes Secrets and how to use them](https://kubernetes.io/docs/concepts/configuration/secret/). + +Now make the following changes to the Ingress and apply them: + +```diff +--- a/ingress.yaml ++++ b/ingress.yaml +@@ -7,7 +7,12 @@ metadata: + kubernetes.io/ingress.class: gce + kubernetes.io/ingress.allow-http: "true" + kubernetes.io/ingress.global-static-ip-name: web-ip ++ cert-manager.io/issuer: letsencrypt-staging + spec: ++ tls: ++ - secretName: web-ssl ++ hosts: ++ - $DOMAIN_NAME + defaultBackend: + service: + name: web +``` + +``` +kubectl apply -f ingress.yaml +``` + +This triggers a complex set of operations which may take many minutes to eventually complete. +Some of these steps take 2-3 minutes and some will initially fail. +They should all eventually succeed because cert-manager and ingress-gce (the Google Cloud ingress controller) will periodically re-reconcile. + +Eventually, When all the pieces are in place, you should be able to +use curl to check the HTTPS connection to your website: + +```bash +curl -v --insecure https://$DOMAIN_NAME +``` + +You should see that the HTTPS connection is established but that the SSL certificate is not trusted; +that's why you use the `--insecure` flag at this stage + +Example output: +```console +* Server certificate: +* subject: CN=www.example.com +* start date: Jul 14 08:52:29 2022 GMT +* expire date: Oct 12 08:52:28 2022 GMT +* issuer: C=US; O=(STAGING) Let's Encrypt; CN=(STAGING) Artificial Apricot R3 +* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. +``` + +> ⏲ You will have to wait 5-10 minutes for the SSL certificate to be signed and then loaded by the Google Cloud load balancer. +> Refer to the [Troubleshooting](#troubleshooting) section if it takes longer. +> +> ℹ️ Adding the annotation `cert-manager.io/issuer: letsencrypt-staging` marks the Ingress for the attention of the cert-manager `ingress-shim` +> and causes it to create a new Certificate with a reference to the Issuer that we created earlier. +> +> 🔰 Read [Securing Ingress Resources](../../usage/ingress.md) to learn more. +> +> 🔰 Read about how to [Specify certificates for your Ingress in GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-multi-ssl#specifying_certificates_for_your_ingress). + +## 9. Create a production ready SSL certificate + +Now that everything is working with the Let's Encrypt staging server, we can switch to the production server and get a trusted SSL certificate. + +Create a Let's Encrypt production Issuer and apply it: + +```yaml +# issuer-lets-encrypt-production.yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-production +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: # ❗ Replace this with your email address + privateKeySecretRef: + name: letsencrypt-production + solvers: + - http01: + ingress: + name: web-ingress +``` + +```bash +kubectl apply -f issuer-lets-encrypt-production.yaml +``` + +Then update the Ingress annotation to use the production Issuer: + +```bash +kubectl annotate ingress web-ingress cert-manager.io/issuer=letsencrypt-production --overwrite +``` + +This will trigger cert-manager to get a new SSL certificate signed by the Let's Encrypt production CA and store it to the `web-ssl` Secret. +Within about 10 minutes, this new certificate will be synced to the Google Cloud load balancer and you will be able to connect to the website using secure HTTPS: + +```bash +curl -v https://$DOMAIN_NAME +``` + +Example output: +```console +... +* Server certificate: +* subject: CN=www.example.com +* start date: Jul 14 09:44:29 2022 GMT +* expire date: Oct 12 09:44:28 2022 GMT +* subjectAltName: host "www.example.com" matched cert's "www.example.com" +* issuer: C=US; O=Let's Encrypt; CN=R3 +* SSL certificate verify ok. +... +Hello, world! +Version: 1.0.0 +Hostname: web-79d88c97d6-t8hj2 +``` + +It should also be possible to visit `https://$DOMAIN_NAME` in your web browser, without any errors or warnings. + +That concludes the tutorial. +You now understand how cert-manager integrates with Kubernetes Ingress and cloud Ingress controllers. +You have learned how to use cert-manager to get free Let's Encrypt SSL certificates. +And you have seen how the certificates can be used by a cloud based load balancer to terminate SSL connections from Internet clients +and forward HTTPS requests to a web server running in your Kubernetes cluster. + +> 💵 Read the [Clean up](#clean-up) section to learn how to delete all the resources that you created in this tutorial and reduce your cloud bill. +> +> 🔰 Read the [Troubleshooting](#troubleshooting) section if you encounter difficulties with the steps described in this tutorial. + +## Clean up + +After completing the tutorial you can clean up by deleting the cluster and the domain name and the static IP as follows: + +```bash +# Delete the cluster and all the Google Cloud resources related to the Ingress that it contains +gcloud container clusters delete $CLUSTER + +# Delete the domain name +gcloud dns record-sets delete $DOMAIN_NAME --zone $ZONE --type A + +# Delete the static IP address +gcloud compute addresses delete web-ip --global +``` + +## Troubleshooting + +When you create or update the Ingress object in this tutorial it triggers a complex set of operations which may take many minutes to eventually complete. +Some of these steps take 2-3 minutes and some will initially fail but then subsequently succeed when either cert-manager or the Google ingress controller re-reconciles. +In short, you should allow 5-10 minutes after you create or change the Ingress and you should expect to see some errors and warnings when you run `kubectl describe ingress web-ingress`. + +Here's a brief summary of the operations performed by cert-manager and ingress-gce (the Google Cloud Ingress controller): + +* cert-manager connects to Let's Encrypt and sends an SSL certificate signing request. +* Let's Encrypt responds with a "challenge", which is a unique token that cert-manager must make available at a well-known location on the target web site. This proves that you are an administrator of that web site and domain name. +* cert-manager deploys a Pod containing a temporary web server that serves the Let's Encrypt challenge token. +* cert-manager reconfigures the Ingress, adding a `rule` to route requests for from Let's Encrypt to that temporary web server. +* Google Cloud ingress controller reconfigures the external HTTP load balancer with that new rule. +* Let's Encrypt now connects and receives the expected challenge token and the signs the SSL certificate and returns it to cert-manager. +* cert-manager stores the signed SSL certificate in the Kubernetes Secret called `web-ssl`. +* Google Cloud ingress controller uploads the signed certificate and associated private key to a Google Cloud certificate. +* Google Cloud ingress controller reconfigures the external load balancer to serve the uploaded SSL certificate. + +### Check Ingress and associated events + +Use `kubectl describe` to view the Ingress configuration and all the associated Events. +Check that the IP address is correct and that the TLS and Host entries match the domain name that you chose for your website. +Notice that `ingress-gce` creates an Event for each of the Google Cloud components that it manages. +And notice that it adds annotations with references to the ID of each of those components. +cert-manager also creates Events when it reconciles the Ingress object, including details of the Certificate object that it creates for the Ingress. + +```console +$ kubectl describe ingress web-ingress +Name: web-ingress +Labels: +Namespace: default +Address: 34.120.159.51 +Ingress Class: +Default backend: web:8080 (10.52.0.13:8080) +TLS: + web-ssl terminates www.example.com +Rules: + Host Path Backends + ---- ---- -------- + * * web:8080 (10.52.0.13:8080) +Annotations: cert-manager.io/issuer: letsencrypt-staging + ingress.kubernetes.io/backends: {"k8s1-01784147-default-web-8080-1647ccd2":"HEALTHY"} + ingress.kubernetes.io/forwarding-rule: k8s2-fr-1lt9dzcy-default-web-ingress-yteotwe4 + ingress.kubernetes.io/https-forwarding-rule: k8s2-fs-1lt9dzcy-default-web-ingress-yteotwe4 + ingress.kubernetes.io/https-target-proxy: k8s2-ts-1lt9dzcy-default-web-ingress-yteotwe4 + ingress.kubernetes.io/ssl-cert: k8s2-cr-1lt9dzcy-4gjeakdb9n7k6ls7-a257650b5fefd174 + ingress.kubernetes.io/target-proxy: k8s2-tp-1lt9dzcy-default-web-ingress-yteotwe4 + ingress.kubernetes.io/url-map: k8s2-um-1lt9dzcy-default-web-ingress-yteotwe4 + kubernetes.io/ingress.allow-http: true + kubernetes.io/ingress.class: gce + kubernetes.io/ingress.global-static-ip-name: web-ip +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CreateCertificate 28m cert-manager-ingress-shim Successfully created Certificate "web-ssl" + Normal Sync 28m loadbalancer-controller UrlMap "k8s2-um-1lt9dzcy-default-web-ingress-yteotwe4" updated + Warning Sync 24m (x16 over 28m) loadbalancer-controller Error syncing to GCP: error running load balancer syncing routine: loadbalancer 1lt9dzcy-default-web-ingress-yteotwe4 does not exist: googleapi: Error 404: The resource 'projects/your-project/global/sslCertificates/k8s2-cr-1lt9dzcy-4gjeakdb9n7k6ls7-e3b0c44298fc1c14' was not found, notFound + Normal Sync 34s (x16 over 65m) loadbalancer-controller Scheduled for sync +``` + +### Use cmctl to show the state of a Certificate and its associated resources + +> ℹ️ [Install `cmctl`](../../reference/cmctl.md) if you have not already done so. + +When you create a Certificate, cert-manager will create a collection of temporary resources +which each contain information about the status of certificate signing process. +You can read more about these in the [Certificate Lifecycle](../../concepts/certificate.md#certificate-lifecycle) section. +Use the `cmctl status` command to view details of all these resources and all the associated Events and error messages. + +You may see some temporary errors, like: + +```console +$ cmctl status certificate web-ssl +Name: web-ssl +Namespace: default +Created at: 2022-07-14T17:30:06+01:00 +Conditions: + Ready: False, Reason: MissingData, Message: Issuing certificate as Secret does not contain a private key + Issuing: True, Reason: MissingData, Message: Issuing certificate as Secret does not contain a private key +DNS Names: +- www.example.com +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Issuing 4m37s cert-manager-certificates-trigger Issuing certificate as Secret does not contain a private key + Normal Generated 4m37s cert-manager-certificates-key-manager Stored new private key in temporary Secret resource "web-ssl-8gsqc" + Normal Requested 4m37s cert-manager-certificates-request-manager Created new CertificateRequest resource "web-ssl-dblrj" +Issuer: + Name: letsencrypt-staging + Kind: Issuer + Conditions: + Ready: True, Reason: ACMEAccountRegistered, Message: The ACME account was registered with the ACME server + Events: +error: 'tls.crt' of Secret "web-ssl" is not set +Not Before: +Not After: +Renewal Time: +CertificateRequest: + Name: web-ssl-dblrj + Namespace: default + Conditions: + Approved: True, Reason: cert-manager.io, Message: Certificate request has been approved by cert-manager.io + Ready: False, Reason: Pending, Message: Waiting on certificate issuance from order default/web-ssl-dblrj-327645514: "pending" + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal cert-manager.io 4m37s cert-manager-certificaterequests-approver Certificate request has been approved by cert-manager.io + Normal OrderCreated 4m37s cert-manager-certificaterequests-issuer-acme Created Order resource default/web-ssl-dblrj-327645514 + Normal OrderPending 4m37s cert-manager-certificaterequests-issuer-acme Waiting on certificate issuance from order default/web-ssl-dblrj-327645514: "" +Order: + Name: web-ssl-dblrj-327645514 + State: pending, Reason: + Authorizations: + URL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/3008789144, Identifier: www.example.com, Initial State: pending, Wildcard: false +Challenges: +- Name: web-ssl-dblrj-327645514-2671694319, Type: HTTP-01, Token: TKspp86xMjQzTvMVXWkezEA2sE2GSWjnld5Lt4X13ro, Key: TKspp86xMjQzTvMVXWkezEA2sE2GSWjnld5Lt4X13ro.f4bppCOm-jXasFGMKjpBE5aQlhiQBeTPIs0Lx822xao, State: pending, Reason: Waiting for HTTP-01 challenge propagation: did not get expected response when querying endpoint, expected "TKspp86xMjQzTvMVXWkezEA2sE2GSWjnld5Lt4X13ro.f4bppCOm-jXasFGMKjpBE5aQlhiQBeTPIs0Lx822xao" but got: Hello, world! +Version: 1... (truncated), Processing: true, Presented: true +``` + +This is because cert-manager is performing a preflight check to see if the temporary challenge web server is reachable at the expected URL. +Initially it will not be reachable, because cert-manager takes some time to deploy the temporary web server and the Ingress controller takes time to set up the new HTTP routing rules. +Eventually you will see that the Certificate is Ready and signed: + +```console +$ cmctl status certificate web-ssl +Name: web-ssl +Namespace: default +Created at: 2022-07-14T17:30:06+01:00 +Conditions: + Ready: True, Reason: Ready, Message: Certificate is up to date and has not expired +DNS Names: +- www.example.com +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Issuing 31m cert-manager-certificates-trigger Issuing certificate as Secret does not contain a private key + Normal Generated 31m cert-manager-certificates-key-manager Stored new private key in temporary Secret resource "web-ssl-8gsqc" + Normal Requested 31m cert-manager-certificates-request-manager Created new CertificateRequest resource "web-ssl-dblrj" + Normal Issuing 26m cert-manager-certificates-issuing The certificate has been successfully issued +Issuer: + Name: letsencrypt-staging + Kind: Issuer + Conditions: + Ready: True, Reason: ACMEAccountRegistered, Message: The ACME account was registered with the ACME server + Events: +Secret: + Name: web-ssl + Issuer Country: US + Issuer Organisation: (STAGING) Let's Encrypt + Issuer Common Name: (STAGING) Artificial Apricot R3 + Key Usage: Digital Signature, Key Encipherment + Extended Key Usages: Server Authentication, Client Authentication + Public Key Algorithm: RSA + Signature Algorithm: SHA256-RSA + Subject Key ID: a51e3621f5c1138947810f27dce425b33c88cb16 + Authority Key ID: de727a48df31c3a650df9f8523df57374b5d2e65 + Serial Number: fa8bb0b603ca2cdbfdfb2872d05ee52cda10 + Events: +Not Before: 2022-07-14T16:34:52+01:00 +Not After: 2022-10-12T16:34:51+01:00 +Renewal Time: 2022-09-12T16:34:51+01:00 +``` + +### Check that the SSL certificate has been copied to Google Cloud + +After cert-manager receives the signed Certificate it stores in the `web-ssl` Secret, +and this in turn triggers the Google Cloud ingress controller to copy that SSL certificate to Google Cloud. +You can see the certificate using the `gcloud` command, as follows: + +```console +$ gcloud compute ssl-certificates list +NAME TYPE CREATION_TIMESTAMP EXPIRE_TIME MANAGED_STATUS +k8s2-cr-1lt9dzcy-4gjeakdb9n7k6ls7-a257650b5fefd174 SELF_MANAGED 2022-07-14T09:37:06.920-07:00 2022-10-12T08:34:51.000-07:00 +``` + +And you can view its contents and check its attributes as follows: + +```console +$ gcloud compute ssl-certificates describe k8s2-cr-1lt9dzcy-4gjeakdb9n7k6ls7-a257650b5fefd174 --format='value(certificate)' \ + | openssl x509 -in - -noout -text +... +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 04:9f:47:f1:cb:25:37:9b:86:a3:ef:bf:2e:77:3b:45:fc:1a + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, O = Let's Encrypt, CN = R3 + Validity + Not Before: Jul 14 17:11:15 2022 GMT + Not After : Oct 12 17:11:14 2022 GMT + Subject: CN = www.example.com +``` + +### Check the Google Cloud forwarding-rules + +After you add the TLS stanza to the Ingress object, you should eventually see a forwarding-rule for the SSL connection: + +```console +$ gcloud compute forwarding-rules describe k8s2-fs-1lt9dzcy-default-web-ingress-yteotwe4 --global +IPAddress: 34.120.159.51 +IPProtocol: TCP +creationTimestamp: '2022-07-14T09:37:12.362-07:00' +description: '{"kubernetes.io/ingress-name": "default/web-ingress"}' +fingerprint: oBTg7dRaIqI= +id: '2303318464959215831' +kind: compute#forwardingRule +labelFingerprint: 42WmSpB8rSM= +loadBalancingScheme: EXTERNAL +name: k8s2-fs-1lt9dzcy-default-web-ingress-yteotwe4 +networkTier: PREMIUM +portRange: 443-443 +selfLink: https://www.googleapis.com/compute/v1/projects/your-project/global/forwardingRules/k8s2-fs-1lt9dzcy-default-web-ingress-yteotwe4 +target: https://www.googleapis.com/compute/v1/projects/your-project/global/targetHttpsProxies/k8s2-ts-1lt9dzcy-default-web-ingress-yteotwe4 +``` diff --git a/content/v1.13-docs/tutorials/getting-started-with-trust-manager/README.md b/content/v1.13-docs/tutorials/getting-started-with-trust-manager/README.md new file mode 100644 index 0000000000..5ce09cbc69 --- /dev/null +++ b/content/v1.13-docs/tutorials/getting-started-with-trust-manager/README.md @@ -0,0 +1,603 @@ +--- +title: Managing public trust in kubernetes with trust-manager +description: Learn how to deploy and configure trust-manager to automatically distribute your approved Public CA configuration to your Kubernetes cluster. +--- + +*Last Verified: 19 June 2023* + +In this tutorial we will walk through how we can use +[trust-manager](https://cert-manager.io/docs/projects/trust-manager/) to +distribute publicly trusted Certificate Authority (CA) certificates inside +a Kubernetes cluster. Once distributed we will also show: + +- How you can automatically reload applications when your trust bundle changes +- How you can enforce applications to use your distributed CA bundle + +From there we will use a simple `curl` pod to show how to automatically mount +the trusted CA `Bundle`, so it can be used without having to configure curl +manually. This mimics how an application would not need any additional +configuration to make use of your trusted CA certificates bundle. + +In this tutorial we will be limiting the scope of our changes to only impact +the `team-a` namespace. To get the most out of these features you will want +to remove this limitation. + +> **Note:** All resources provided are demonstrative and should be reviewed + properly before using in production environments. + +## Prerequisites + +**💻 Software** + +1. [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl): The Kubernetes +command-line tool which allows you to configure Kubernetes clusters. +1. [helm](https://helm.sh/): A package manager for Kubernetes. +1. [yq](https://github.com/mikefarah/yq#install): A command line tool for +parsing YAML with helpful coloring. + +## Distribute Public CA Trust + +Let us first setup trust-manager and have our public CAs distributed to our +demo namespace: `team-a`. + +### Setup Application & Bundle + +1) Install trust-manager following the + [instructions here](../../projects/trust-manager/README.md#installation). + +1) Create your first `Bundle` resource including only Public CA certificates + + ```yaml file=./trust/bundle-public.yaml + ``` + + ```shell + kubectl apply -f - < **Note**: this is to limit the scope of our trust bundle to only the + `team-a` namespace as mentioned previously. + +1) Verify that the trust-manager controller has correctly propagated the + CA bundle to the namespace: + + ```shell + kubectl get configmap -n team-a public-bundle -o yaml + ``` + + Note that this output should be quite long. This is because the default + public bundle that we use has a lot of public CAs in it. + +### Mount Trust Bundle to Application with Automatic Use + +To use our trusted CAs we will mount them to the application in a default +location that most applications expect to find a `ca-certificates.crt` file. +The benefit to this approach is that most application code inside the container +will use this file by default and not require any additional configuration. There is the added benefit that you will be mounting over the top of +`/etc/ssl/certs` which will remove existing CA certificates, usually +present from a container base image or pulled in during CI builds. + +> **WARNING:** We have chosen one well known location in this example which + is used by alpine and `curl` for sourcing trusted CAs. This is not the only + location that can be used, so a container may have other default locations. + If you want to see where default CAs are located you can use + [paranoia](https://github.com/jetstack/paranoia) to inspect a built container + image. + +1) Apply the application deployment: + + ```yaml file=./trust/deploy-auto.yaml + ``` + + ```shell + kubectl apply -f - < ..data/ca-certificates.crt + ``` + + Note that normally this container image the output would look something + like the following, when there is no volume overriding this directory: + + ``` + ~ $ ls -ltr /etc/ssl/certs/ + total 608 + -rw-r--r-- 1 root root 214222 Apr 14 01:11 ca-certificates.crt + lrwxrwxrwx 1 root root 52 Apr 14 01:11 ca-cert-vTrus_Root_CA.pem -> /usr/share/ca-certificates/mozilla/vTrus_Root_CA.crt + lrwxrwxrwx 1 root root 56 Apr 14 01:11 ca-cert-vTrus_ECC_Root_CA.pem -> /usr/share/ca-certificates/mozilla/vTrus_ECC_Root_CA.crt + ... + lrwxrwxrwx 1 root root 53 Apr 14 01:11 02265526.0 -> ca-cert-Entrust_Root_Certification_Authority_-_G2.pem + lrwxrwxrwx 1 root root 31 Apr 14 01:11 002c0b4f.0 -> ca-cert-GlobalSign_Root_R46.pem + ``` + +1) Make a HTTPS call out to a well known site to validate `curl` works without +having to pass the additional `--cacert` flag: + + ```shell + curl -v https://bbc.co.uk/news + ``` + + Success will result in a valid TLS connection such as: + + ``` + * Trying 151.101.0.81:443... + * Connected to bbc.co.uk (151.101.0.81) port 443 (#0) + * ALPN: offers h2,http/1.1 + * TLSv1.3 (OUT), TLS handshake, Client hello (1): + * CAfile: /etc/ssl/certs/ca-certificates.crt + * CApath: none + * TLSv1.3 (IN), TLS handshake, Server hello (2): + * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): + * TLSv1.3 (IN), TLS handshake, Certificate (11): + * TLSv1.3 (IN), TLS handshake, CERT verify (15): + * TLSv1.3 (IN), TLS handshake, Finished (20): + * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): + * TLSv1.3 (OUT), TLS handshake, Finished (20): + * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 + * ALPN: server accepted h2 + * Server certificate: + * subject: C=GB; ST=London; L=London; O=BRITISH BROADCASTING CORPORATION; CN=www.bbc.com + * start date: Mar 14 06:16:13 2023 GMT + * expire date: Apr 14 06:16:12 2024 GMT + * subjectAltName: host "bbc.co.uk" matched cert's "bbc.co.uk" + * issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign RSA OV SSL CA 2018 + * SSL certificate verify ok. + ``` + +1. Exit the container: `exit` + +## Configure Real Applications + +Based on the example above, Kubernetes is able to mount over the top of the +default CA certificate bundle. You can use this with applications assuming you +know where the default locations they retrieve CA certificates from. + +For example with `Go` your application is configurable with either +`SSL_CERT_FILE` or `SSL_CERT_DIR` to point to the default CA certificate +file location. + +See more details [here](https://go.dev/src/crypto/x509/root_unix.go) and +for the default locations on various OS bases, check +[here](https://go.dev/src/crypto/x509/root_linux.go) + +```go +// Possible certificate files; stop after finding one. +var certFiles = []string{ + "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. + "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6 + "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/pki/tls/cacert.pem", // OpenELEC + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 + "/etc/ssl/cert.pem", // Alpine Linux +} + +// Possible directories with certificate files; all will be read. +var certDirectories = []string{ + "/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139 + "/etc/pki/tls/certs", // Fedora/RHEL + "/ +``` + +Having checked Python the `ssl` library uses the same two environment variables +for finding the trusted CAs: `SSL_CERT_DIR` and / or `SSL_CERT_FILE`. You can +verify this [in documentation](https://docs.python.org/3/library/ssl.html#ssl.get_default_verify_paths) +and from a `python3` runtime: + +```python3 +>>> import ssl +>>> ssl.get_default_verify_paths() +DefaultVerifyPaths(cafile=None, capath='/usr/lib/ssl/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/usr/lib/ssl/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/usr/lib/ssl/certs') +``` + +This should mean that any CAs mounted in a file and any of the following files +will be trusted by any python application runtime, similar to `Go`: + +- '/usr/lib/ssl/cert.pem' +- '/usr/lib/ssl/certs/*' + +Similar could be achieved with other languages. + +## Automate and Enforce + +So now we have mounted trust-manager's bundle manually, you might be thinking: + +- What happens if the CA Bundle is changed, how do I get that change to my + application? +- How do I ensure that my CA Bundle is mounted to all applications in my + cluster without having to request changes from my tenants? + +Let's tackle both of these scenarios using additional Open Source tools. + +### Rollout CA Bundle Changes + +If your CA bundle changes, those changes will be synced to the namespaces +pretty quickly. This change will be reflected in the volume attached to the +container, but most applications will not pickup on the file system change. +The common approach is restarting the client application deployment, through +the use of `kubectl rollout restart deployment `. There is an +option to automate this process through a third party piece of open-source +software. + +Using [Stakater Reloader](https://github.com/stakater/Reloader) it is +possible to reload or rollout a deployment whenever a `ConfigMap` or `Secret` +changes. So whenever the `Bundle`'s target is synced, the Reloader component +can pick up this change and rollout applications mounting those resource +as volumes or environment variables. + +**Please note** that there are many alternative pieces of software that you +could bundle or write into your application container. They would simply watch +the file system for changes and trigger a reload of the application process. +Such an approach requires container image or code changes and this could be +difficult to implement with many tenants. The advantage to using reloader here +is that it's a generic solution, applicable to all applications running in a +cluster. + +1. Continuing with the reloader, it can be installed with helm: + + ```shell + helm repo add stakater https://stakater.github.io/stakater-charts + helm repo update + helm install reloader stakater/reloader -n stakater-reloader --create-namespace --set fullnameOverride=reloader + ``` + +1. We can reuse the deployment `sleep-auto` from the previous section and + configured it to enabled the reload functionality: + + ```shell + kubectl annotate deployment -n team-a sleep-auto reloader.stakater.com/auto="true" + ``` + + **Please note** there are several configuration options to configure + the reloader tooling and this is only the most basic example. Refer to + [the documentation](https://github.com/stakater/Reloader#how-to-use-reloader) + for more detailed examples. + +1. In another terminal watch the application rollout: + + ```shell + kubectl get po -n team-a -w + ``` + +1. To test this change we can edit our `Bundle` resource to remove all the + default Public CA certificates and only provide one CA certificate instead: + + ```yaml file=./trust/bundle-one-ca.yaml + ``` + + ```shell + kubectl apply -f - < ca.pem + +# Out of interest, we can check out what our CA looks like +openssl x509 -in ca.pem -noout -text + +# Add our CA to a secret +kubectl create secret generic -n cert-manager istio-root-ca --from-file=ca.pem=ca.pem +``` + +## Installing istio-csr + +istio-csr is best installed via Helm, and it should be simple and quick to install. There +are a bunch of other configuration options for the helm chart, which you can check out [here](../../projects/istio-csr.md). + +```console +helm repo add jetstack https://charts.jetstack.io +helm repo update + +# We set a few helm template values so we can point at our static root CA +helm install -n cert-manager cert-manager-istio-csr jetstack/cert-manager-istio-csr \ + --set "app.tls.rootCAFile=/var/run/secrets/istio-csr/ca.pem" \ + --set "volumeMounts[0].name=root-ca" \ + --set "volumeMounts[0].mountPath=/var/run/secrets/istio-csr" \ + --set "volumes[0].name=root-ca" \ + --set "volumes[0].secret.secretName=istio-root-ca" + +# Check to see that the istio-csr pod is running and ready +kubectl get pods -n cert-manager +NAME READY STATUS RESTARTS AGE +cert-manager-aaaaaaaaaa-11111 1/1 Running 0 9m46s +cert-manager-cainjector-aaaaaaaaaa-22222 1/1 Running 0 9m46s +cert-manager-istio-csr-bbbbbbbbbb-00000 1/1 Running 0 63s +cert-manager-webhook-aaaaaaaaa-33333 1/1 Running 0 9m46s +``` + +## Installing Istio + +If you're not running on kind, you may need to do some additional [setup tasks](https://istio.io/latest/docs/setup/platform-setup/) before installing Istio. + +We use the `istioctl` CLI to install Istio, configured using a custom IstioOperator manifest. + +The custom manifest does the following: + +- Disables the CA server in istiod, +- Ensures that Istio workloads request certificates from istio-csr, +- Ensures that the istiod certificates and keys are mounted from the Certificate created when installing istio-csr. + +First we download our demo manifest and then we apply it. + +```console +curl -sSL https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/istio-csr/example/istio-config-getting-started.yaml > istio-install-config.yaml +``` + +You may wish to inspect and tweak `istio-install-config.yaml` if you know what you're doing, +but this manifest should work for example purposes as-is. + +If you set a custom `app.tls.trustDomain` when installing istio-csr via helm earlier, you'll need to ensure that +value is repeated in `istio-install-config.yaml`. + +This final command will install Istio; the exact command you need might vary on different platforms, +and will certainly vary on OpenShift. + +```console +# This takes a little time to complete +istioctl install -f istio-install-config.yaml + +# If you're on OpenShift, you need a different profile: +# istioctl install --set profile=openshift -f istio-install-config.yaml +``` + +You will be prompted for input to confirm your choice of Istio profile: + +```console +This will install the Istio 1.14.1 demo profile with ["Istio core" "Istiod" "Ingress gateways" "Egress gateways"] components into the cluster. Proceed? (y/N) +``` + +Confirm your selection by entering `y` into the console to proceed with installation. + +## Validating Install + +The following steps are option but can be followed to validate everything is hooked correctly: + +1. Deploy a sample application & watch for `certificaterequests.cert-manager.io` resources +2. Verify `cert-manager` logs for new `certificaterequests` and responses +3. Verify the CA Endpoint being used in a `istio-proxy` sidecar container +4. Using `istioctl` to fetch the certificate info for the `istio-proxy` container + +To see this all in action, lets deploy a very simple sample application from the +[Istio samples](https://github.com/istio/istio/tree/master/samples/httpbin). + +First set some environment variables whose values could be changed if needed: + +```shell +# Set namespace for sample application +export NAMESPACE=default +# Set env var for the value of the app label in manifests +export APP=httpbin +# Grab the installed version of istio +export ISTIO_VERSION=$(istioctl version -o json | jq -r '.meshVersion[0].Info.version') +``` + +We use the `default` namespace for simplicity, so let's label the namespace for Istio injection: + +```shell +kubectl label namespace $NAMESPACE istio-injection=enabled --overwrite +``` + +In a separate terminal you should now follow the logs for `cert-manager`: + +```shell +kubectl logs -n cert-manager $(kubectl get pods -n cert-manager -o jsonpath='{.items..metadata.name}' --selector app=cert-manager) --since 2m -f +``` + +In another separate terminal, lets watch the `istio-system` namespace for `certificaterequests`: + +```shell +kubectl get certificaterequests.cert-manager.io -n istio-system -w +``` + +Now deploy the sample application `httpbin` in the labeled namespace. Note the use of a +variable to match the manifest version to your installed Istio version: + +```shell +kubectl apply -n $NAMESPACE -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/httpbin/httpbin.yaml +``` + +You should see something similar to the output here for `certificaterequests`: + +``` +NAME APPROVED DENIED READY ISSUER REQUESTOR AGE +istio-ca-74bnl True True selfsigned system:serviceaccount:cert-manager:cert-manager 2d2h +istiod-w9zh6 True True istio-ca system:serviceaccount:cert-manager:cert-manager 27m +istio-csr-8ddcs istio-ca system:serviceaccount:cert-manager:cert-manager-istio-csr 0s +istio-csr-8ddcs True istio-ca system:serviceaccount:cert-manager:cert-manager-istio-csr 0s +istio-csr-8ddcs True True istio-ca system:serviceaccount:cert-manager:cert-manager-istio-csr 0s +istio-csr-8ddcs True True istio-ca system:serviceaccount:cert-manager:cert-manager-istio-csr 0s +``` + +The key request being `istio-csr-8ddcs` in our example output. You should then check your +`cert-manager` log output for two log lines with this request being "Approved" and "Ready": + +``` +I0113 16:51:59.186482 1 conditions.go:261] Setting lastTransitionTime for CertificateRequest "istio-csr-8ddcs" condition "Approved" to 2022-01-13 16:51:59.186455713 +0000 UTC m=+3507.098466775 +I0113 16:51:59.258876 1 conditions.go:261] Setting lastTransitionTime for CertificateRequest "istio-csr-8ddcs" condition "Ready" to 2022-01-13 16:51:59.258837897 +0000 UTC m=+3507.170859959 +``` + +You should now see the application is running with both the application container and the sidecar: + +```shell +~ kubectl get pods -n $NAMESPACE +NAME READY STATUS RESTARTS AGE +httpbin-74fb669cc6-559cg 2/2 Running 0 4m +``` + +To validate that the `istio-proxy` sidecar container has requested the certificate from the correct +service, check the container logs: + +```shell +kubectl logs $(kubectl get pod -n $NAMESPACE -o jsonpath="{.items...metadata.name}" --selector app=$APP) -c istio-proxy +``` + +You should see some early logs similar to this example: + +Istio v1.12 and earlier versions: + +``` +2022-01-13T16:51:58.495493Z info CA Endpoint cert-manager-istio-csr.cert-manager.svc:443, provider Citadel +2022-01-13T16:51:58.495817Z info Using CA cert-manager-istio-csr.cert-manager.svc:443 cert with certs: var/run/secrets/istio/root-cert.pem +2022-01-13T16:51:58.495941Z info citadelclient Citadel client using custom root cert: cert-manager-istio-csr.cert-manager.svc:443 +``` + +Istio v1.13+ + +``` +2022-01-13T16:51:58.495493Z info CA Endpoint cert-manager-istio-csr.cert-manager.svc:443, provider Citadel +2022-01-13T16:51:58.495817Z info Using CA cert-manager-istio-csr.cert-manager.svc:443 cert with certs: var/run/secrets/istio/root-cert.pem +2022-01-13T16:51:58.495941Z info citadelclient Citadel client using custom root cert: var/run/secrets/istio/root-cert.pem +``` + +Finally we can inspect the certificate being used in memory by Envoy. This one liner should return you the certificate being used: + +```shell +istioctl proxy-config secret $(kubectl get pods -n $NAMESPACE -o jsonpath='{.items..metadata.name}' --selector app=$APP) -o json | jq -r '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' | base64 --decode | openssl x509 -text -noout +``` + +In particular look for the following sections: + +``` + Signature Algorithm: ecdsa-with-SHA256 + Issuer: O=cert-manager, O=cluster.local, CN=istio-ca + Validity + Not Before: Jan 13 16:51:59 2022 GMT + Not After : Jan 13 17:51:59 2022 GMT +... + X509v3 Subject Alternative Name: + URI:spiffe://cluster.local/ns/default/sa/httpbin +``` + +You should see the relevant Trust Domain inside the Issuer. In the default case, it should be: +`cluster.local` as above. Note that the SPIFFE URI may be different if you used a different +namespace or application. + +## Clean up + +Assuming your running inside kind, you can simply remove the cluster: + +```shell +kind delete cluster diff --git a/content/v1.13-docs/tutorials/syncing-secrets-across-namespaces.md b/content/v1.13-docs/tutorials/syncing-secrets-across-namespaces.md new file mode 100644 index 0000000000..95e8a6a5fa --- /dev/null +++ b/content/v1.13-docs/tutorials/syncing-secrets-across-namespaces.md @@ -0,0 +1,143 @@ +--- +title: Syncing Secrets Across Namespaces +description: | + Learn how to synchronize Kubernetes Secret resources across namespaces + using extensions such as: reflector, kubed and kubernetes-replicator. +--- + +It may be required for multiple components across namespaces to consume the same +`Secret` that has been created by a single `Certificate`. The recommended way to +do this is to use extensions such as: + - [reflector](https://github.com/emberstack/kubernetes-reflector) with support + for auto secret reflection + - [kubed](https://github.com/appscode/kubed) with its + [secret syncing feature](https://appscode.com/products/kubed/v0.11.0/guides/config-syncer/intra-cluster/) + - [kubernetes-replicator](https://github.com/mittwald/kubernetes-replicator) secret replication + +## Serving a wildcard to ingress resources in different namespaces (default SSL certificate) + +Most ingress controllers, including [ingress-nginx](https://kubernetes.github.io/ingress-nginx/user-guide/tls/#default-ssl-certificate), [Traefik](https://docs.traefik.io/https/tls/#default-certificate), and [Kong](https://docs.konghq.com/2.0.x/configuration/#ssl_cert) support specifying a _single_ certificate to be used for ingress resources which request TLS but do not specify `tls.[].secretName`. This is often referred to as a "default SSL certificate". As long as this is correctly configured, ingress resources in any namespace will be able to use a single wildcard certificate. Wildcard certificates are not supported with HTTP01 validation and require DNS01. + +Sample ingress snippet: + +``` +apiVersion: networking.k8s.io/v1 +kind: Ingress +#[...] +spec: + rules: + - host: service.example.com + #[...] + tls: + - hosts: + - service.example.com + #secretName omitted to use default wildcard certificate +``` + + +## Syncing arbitrary secrets across namespaces using extensions + +In order for the target Secret to be synced, you can use the `secretTemplate` field +for annotating the generated secret with the extension specific annotation (See [CertificateSecretTemplate]). + + +### Using `reflector` + The example below shows syncing a certificate's secret from the `cert-manager` namespace to multiple namespaces (i.e. `dev`, `staging`, `prod`). + Reflector will ensure that any namespace (existing or new) matching the allowed condition (with regex support) will get a copy of the certificate's secret and will keep it up to date. + You can also sync other secrets (different name) using `reflector` (consult the extension's [README](https://github.com/emberstack/kubernetes-reflector/blob/main/README.md)) + +```yaml +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: source + namespace: cert-manager +spec: + secretName: source-tls + commonName: source + issuerRef: + name: source-ca + kind: Issuer + group: cert-manager.io + secretTemplate: + annotations: + reflector.v1.k8s.emberstack.com/reflection-allowed: "true" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "dev,staging,prod" # Control destination namespaces + reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" # Auto create reflection for matching namespaces + reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "dev,staging,prod" # Control auto-reflection namespaces +``` + + +### Using `kubed` + The example below shows syncing +a certificate belonging to the `sandbox` Certificate from the `cert-manager` +namespace, into the `sandbox` namespace. + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: sandbox + labels: + cert-manager-tls: sandbox # Define namespace label for kubed +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: sandbox + namespace: cert-manager +spec: + secretName: sandbox-tls + commonName: sandbox + issuerRef: + name: sandbox-ca + kind: Issuer + group: cert-manager.io + secretTemplate: + annotations: + kubed.appscode.com/sync: "cert-manager-tls=sandbox" # Sync certificate to matching namespaces +``` + + +### Using `kubernetes-replicator` +Replicator supports both push- and pull-based replication. Push-based +replication will "push out" the TLS secret into namespaces when new ones are +created, or when the secret changes. Pull-based replication makes it possible +to create an empty TLS secret in the destination namespace and select a +"source" resource from which the data is replicated from. The following example +shows the pull-based approach: +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: source + namespace: cert-manager +spec: + secretName: source-tls + commonName: source + issuerRef: + name: source-ca + kind: Issuer + secretTemplate: + annotations: + replicator.v1.mittwald.de/replication-allowed: "true" # permit replication + replicator.v1.mittwald.de/replication-allowed-namespaces: "dev,test,prod-[0-9]*" # comma separated list of namespaces or regular expressions +--- +apiVersion: v1 +kind: Secret +metadata: + name: tls-secret-replica + namespace: prod-1 + annotations: + replicator.v1.mittwald.de/replicate-from: cert-manager/source-tls +type: kubernetes.io/tls +# Normally, we'd create an empty destination secret, but secrets of type +# 'kubernetes.io/tls' are treated in a special way and need to have properties +# data["tls.crt"] and data["tls.key"] to begin with, though they may be empty. +data: + tls.key: "" + tls.crt: "" +``` + +[CertificateSecretTemplate]: ../reference/api-docs.md#cert-manager.io/v1.CertificateSecretTemplate diff --git a/content/v1.13-docs/tutorials/venafi/venafi.md b/content/v1.13-docs/tutorials/venafi/venafi.md new file mode 100644 index 0000000000..d1a83964b9 --- /dev/null +++ b/content/v1.13-docs/tutorials/venafi/venafi.md @@ -0,0 +1,585 @@ +--- +title: Securing Ingresses with Venafi +description: 'cert-manager tutorials: Securing Ingress using Venafi Issuers' +--- + +This guide walks you through how to secure a Kubernetes +[`Ingress`](https://kubernetes.io/docs/concepts/services-networking/ingress/) +resource using the Venafi Issuer type. + +Whilst stepping through, you will learn how to: + +- Create an EKS cluster using [`eksctl`](https://github.com/weaveworks/eksctl) +- Install cert-manager into the EKS cluster +- Deploy [`nginx-ingress`](https://github.com/kubernetes/ingress-nginx) to + expose applications running in the cluster +- Configure a Venafi Cloud issuer +- Configure cert-manager to secure your application traffic + +While this guide focuses on EKS as a Kubernetes provisioner and Venafi +as a Certificate issuer, the steps here should be generally re-usable for other +Issuer types. + +## Prerequisites + +- An AWS account +- `kubectl` installed +- Access to a publicly registered DNS zone +- A Venafi Cloud account and API credentials + +## Create an EKS cluster + +If you already have a running EKS cluster you can skip this step and move onto +deploying cert-manager. + +[`eksctl`](https://eksctl.io/introduction/installation/) is a tool that makes it +easier to deploy and manage an EKS cluster. + +Installation instructions for various platforms can be found in the +[`eksctl` installation +instructions](https://eksctl.io/introduction/installation/). + +Once installed, you can create a basic cluster by running: + +``` +$ eksctl create cluster +``` + +This process may take up to 20 minutes to complete. Complete instructions on +using `eksctl` can be found in the [`eksctl` usage +section](https://eksctl.io/usage/creating-and-managing-clusters/). + +Once your cluster has been created, you should verify that your cluster is +running correctly by running the following command: + +``` +$ kubectl get pods --all-namespaces +NAME READY STATUS RESTARTS AGE +aws-node-8xpkp 1/1 Running 0 115s +aws-node-tflxs 1/1 Running 0 118s +coredns-694d9447b-66vlp 1/1 Running 0 23s +coredns-694d9447b-w5bg8 1/1 Running 0 23s +kube-proxy-4dvpj 1/1 Running 0 115s +kube-proxy-tpvht 1/1 Running 0 118s +``` + +You should see output similar to the above, with all pods in a Running state. + +## Installing cert-manager + +There are no special requirements to note when installing cert-manager on EKS, +so the regular [running on +Kubernetes](../../installation/README.md) guides +can be used to install cert-manager. + +Please walk through the installation guide and return to this step once you +have validated cert-manager is deployed correctly. + +## Installing `ingress-nginx` + +A [Kubernetes ingress +controller](https://eksctl.io/usage/creating-and-managing-clusters/) is designed +to be the access point for HTTP and HTTPS traffic to the software running within +your cluster. The [`ingress-nginx`](https://github.com/kubernetes/ingress-nginx) +controller does this by providing an HTTP proxy service supported by your cloud +provider's load balancer (in this case, a [Network Load Balancer +(NLB)](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html). + +You can get more details about `ingress-nginx` and how it works from the +[documentation for `ingress-nginx`](https://kubernetes.github.io/ingress-nginx/). + +To deploy `ingress-nginx` using an ELB to expose the service, run the following: + +Deploy the AWS specific prerequisite manifest +```bash +$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.4.0/deploy/static/provider/aws/deploy.yaml +``` + +Deploy the 'generic' `ingress-nginx` manifest +```bash +$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/aws/deploy.yaml +``` + +You may have to wait up to 5 minutes for all the required components in your +cluster and AWS account to become ready. + +You can run the following command to determine the address that Amazon has +assigned to your NLB: + +```bash +$ kubectl get service -n ingress-nginx +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +ingress-nginx LoadBalancer 10.100.52.175 a8c2870a5a8a311e9a9a10a2e7af57d7-6c2ec8ede48726ab.elb.eu-west-1.amazonaws.com 80:31649/TCP,443:30567/TCP 4m10s +``` + +The *EXTERNAL-IP* field may say `` for a while. This indicates the NLB +is still being created. Retry the command until an *EXTERNAL-IP* has been +provisioned. + +Once the *EXTERNAL-IP* is available, you should run the following command to +verify that traffic is being correctly routed to `ingress-nginx`: + +``` +$ curl http://a8c2870a5a8a311e9a9a10a2e7af57d7-6c2ec8ede48726ab.elb.eu-west-1.amazonaws.com/ + +404 Not Found + +

              404 Not Found

              +
              openresty/1.15.8.1
              + + +``` + +Whilst the above message would normally indicate an error (the page not being +found), in this instance it indicates that traffic is being correctly routed to +the `ingress-nginx` service. + +> Note: Although the AWS Application Load Balancer (ALB) is a modern load +> balancer offered by AWS that can can be provisioned from within EKS, at the +> time of writing, the +> [`alb-ingress-controller`](https://github.com/kubernetes-sigs/aws-alb-ingress-controller>) +> is only capable of serving sites using certificates stored in AWS Certificate +> Manager (ACM). Version 1.15 of Kubernetes should address multiple bug fixes +> for this controller and allow for TLS termination support. + +## Configure your DNS records + +Now that our NLB has been provisioned, we should point our application's DNS +records at the NLBs address. + +Go into your DNS provider's console and set a CNAME record pointing to your +NLB. + +For the purposes of demonstration, we will assume in this guide you have created +the following DNS entry: + +``` +example.com CNAME a8c2870a5a8a311e9a9a10a2e7af57d7-6c2ec8ede48726ab.elb.eu-west-1.amazonaws.com +``` + +As you progress through the rest of this tutorial, please replace `example.com` +with your own registered domain. + +## Deploying a demo application + +For the purposes of this demo, we provide an example deployment which is a +simple "hello world" website. + +First, create a new namespace that will contain your application: + +```bash +$ kubectl create namespace demo +namespace/demo created +``` + +Save the following YAML into a file named `demo-deployment.yaml`: + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: hello-kubernetes + namespace: demo +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 8080 + selector: + app: hello-kubernetes +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hello-kubernetes + namespace: demo +spec: + replicas: 2 + selector: + matchLabels: + app: hello-kubernetes + template: + metadata: + labels: + app: hello-kubernetes + spec: + containers: + - name: hello-kubernetes + image: paulbouwer/hello-kubernetes:1.5 + resources: + requests: + cpu: 100m + memory: 100Mi + ports: + - containerPort: 8080 +``` + +Then run: + +```bash +kubectl apply -n demo -f demo-deployment.yaml +``` + +Note that the Service resource we deploy is of type `ClusterIP` and not +`LoadBalancer`, as we will expose and secure traffic for this service using +`ingress-nginx` that we deployed earlier. + +You should be able to see two Pods and one Service in the `demo` namespace: + +```bash +kubectl get po,svc -n demo +NAME READY STATUS RESTARTS AGE +hello-kubernetes-66d45d6dff-m2lnr 1/1 Running 0 7s +hello-kubernetes-66d45d6dff-qt2kb 1/1 Running 0 7s + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/hello-kubernetes ClusterIP 10.100.164.58 80/TCP 7s +``` + +Note that we have not yet exposed this application to be accessible over the +internet. We will expose the demo application to the internet in later steps. + +## Creating a Venafi Issuer resource + +cert-manager supports both Venafi TPP and Venafi Cloud. + +Please only follow one of the below sections according to where you want to +retrieve your Certificates from. + +### Venafi TPP + +Assuming you already have a Venafi TPP server set up properly, you can create +a Venafi Issuer resource that can be used to issue certificates. + +To do this, you need to make sure you have your TPP *username* and *password*. + +In order for cert-manager to be able to authenticate with your Venafi TPP +server and set up an Issuer resource, you'll need to create a Kubernetes +Secret containing your username and password: + +```bash +$ kubectl create secret generic \ + venafi-tpp-secret \ + --namespace=demo \ + --from-literal=username='YOUR_TPP_USERNAME_HERE' \ + --from-literal=password='YOUR_TPP_PASSWORD_HERE' +``` + +We must then create a Venafi Issuer resource, which represents a certificate +authority within Kubernetes. + +Save the following YAML into a file named `venafi-issuer.yaml`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: venafi-issuer + namespace: demo +spec: + venafi: + zone: "Default" # Set this to the Venafi policy zone you want to use + tpp: + url: https://venafi-tpp.example.com/vedsdk # Change this to the URL of your TPP instance + caBundle: + credentialsRef: + name: venafi-tpp-secret +``` + +Then run: + +```bash +$ kubectl apply -n demo -f venafi-issuer.yaml +``` + +When you run the following command, you should see that the Status stanza of +the output shows that the Issuer is Ready (i.e. has successfully validated +itself with the Venafi TPP server). + +```bash +$ kubectl describe issuer -n demo venafi-issuer + + Status: + Conditions: + Last Transition Time: 2019-07-17T15:46:00Z + Message: Venafi issuer started + Reason: Venafi issuer started + Status: True + Type: Ready + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Ready 14s cert-manager Verified issuer with Venafi server +``` + +### Venafi Cloud + +You can sign up for a Venafi Cloud account by visiting the [enrollment +page](https://www.venafi.com/cloud). + +Once registered, you should fetch your API key by clicking your name in the top +right of the control panel interface. + +In order for cert-manager to be able to authenticate with your Venafi Cloud +account and set up an Issuer resource, you'll need to create a Kubernetes +Secret containing your API key: + +```bash +$ kubectl create secret generic \ + venafi-cloud-secret \ + --namespace=demo \ + --from-literal=apikey= +``` + +We must then create a Venafi Issuer resource, which represents a certificate +authority within Kubernetes. + +Save the following YAML into a file named `venafi-issuer.yaml`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: venafi-issuer + namespace: demo +spec: + venafi: + zone: "Default" # Set this to the Venafi policy zone you want to use + cloud: + apiTokenSecretRef: + name: venafi-cloud-secret + key: apikey +``` + +Then run: + +```bash +$ kubectl apply -n demo -f venafi-issuer.yaml +``` + +When you run the following command, you should see that the Status stanza of +the output shows that the Issuer is Ready (i.e. has successfully validated +itself with the Venafi Cloud service). + +```bash +$ kubectl describe issuer -n demo venafi-issuer +... +Status: + Conditions: + Last Transition Time: 2019-07-17T15:46:00Z + Message: Venafi issuer started + Reason: Venafi issuer started + Status: True + Type: Ready +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Ready 14s cert-manager Verified issuer with Venafi server +``` + +## Request a Certificate + +Now that the Issuer is configured and we have confirmed it has been set up +correctly, we can begin requesting certificates which can be used by Kubernetes +applications. + +Full information on how to specify and request Certificate resources can be +found in the [Requesting Certificates](../../usage/certificate.md) guide. + +For now, we will create a basic X.509 Certificate that is valid for our domain, +`example.com`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-tls + namespace: demo +spec: + secretName: example-com-tls + dnsNames: + - example.com + commonName: example.com + issuerRef: + name: venafi-issuer +``` + +Save this YAML into a file named `example-com-tls.yaml` and run: + +```bash +$ kubectl apply -n demo -f example-com-tls.yaml +``` + +As long as you've ensured that the zone of your Venafi Cloud account (in our +example, we use the "Default" zone) has been configured with a CA or contains a +custom certificate, cert-manager can now take steps to populate the +`example-com-tls` Secret with a certificate. It does this by identifying itself +with Venafi Cloud using the API key, then requesting a certificate to match the +specifications of the Certificate resource that we've created. + +You can run `kubectl describe` to check the progress of your Certificate: + +```bash +$ kubectl describe certificate -n demo example-com-tls +... +Status: + Conditions: + Last Transition Time: 2019-07-17T17:43:01Z + Message: Certificate is up to date and has not expired + Reason: Ready + Status: True + Type: Ready + Not After: 2019-10-15T12:00:00Z +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Issuing 33s cert-manager Requesting new certificate... + Normal GenerateKey 33s cert-manager Generated new private key + Normal Validate 33s cert-manager Validated certificate request against Venafi zone policy + Normal Requesting 33s cert-manager Requesting certificate from Venafi server... + Normal Retrieve 15s cert-manager Retrieved certificate from Venafi server + Normal CertIssued 15s cert-manager Certificate issued successfully +``` + +Once the Certificate has been issued, you should see events similar to above. + +You should then be able to see the certificate has been successfully stored in +the Secret resource: + +```bash +$ kubectl get secret -n demo example-com-tls +NAME TYPE DATA AGE +example-com-tls kubernetes.io/tls 3 2m47s + +$ kubectl get secret example-com-tls -o 'go-template={{index .data "tls.crt"}}' | \ + base64 --decode | \ + openssl x509 -noout -text +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 0d:ce:bf:89:04:d4:41:83:f4:4c:32:66:64:fb:60:14 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, O=DigiCert Inc, CN=DigiCert Test SHA2 Intermediate CA-1 + Validity + Not Before: Jul 17 00:00:00 2019 GMT + Not After : Oct 15 12:00:00 2019 GMT + Subject: C=US, ST=California, L=Palo Alto, O=Venafi Cloud, OU=SerialNumber, CN=example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:ad:2e:66:02:20:c9:b1:6a:00:63:70:4e:22:3c: + 45:63:6e:e7:fd:4c:94:7d:75:50:22:a2:01:72:99: + 9c:23:04:90:51:85:4d:47:32:e4:8b:ee:b1:ea:09: + 1a:de:97:5d:31:05:a2:73:73:4f:06:a3:b2:59:ee: + bc:30:f7:26:85:3d:b3:56:e4:c2:97:34:b6:ac:6d: + 65:7e:a2:4e:b4:ce:f2:0a:0a:4c:d7:32:d7:5a:18: + e8:69:c6:34:28:26:36:ef:c5:bc:ae:ba:ca:d2:46: + 3f:d4:61:39:66:8f:19:cc:d6:d6:10:77:af:51:93: + 1b:4d:f8:d1:10:19:ab:ac:b3:7b:0b:98:58:29:e6: + a9:ac:9f:7a:dc:63:0d:51:f5:bd:9f:f3:03:2e:b3: + 2d:2f:00:87:f4:e1:cd:5a:32:c6:d8:fb:49:c4:e7: + da:3f:0f:8f:bb:66:94:28:5d:99:fe:7c:f0:17:1b: + fd:3e:ed:dd:36:bf:8e:62:60:0c:85:7f:76:74:4b: + 37:d9:c2:e8:74:49:04:bf:f1:83:81:cc:4f:9b:f3: + 40:97:d4:dc:b6:d3:2d:dc:73:18:93:48:a5:8f:6c: + 57:7f:ec:62:c0:bc:c2:b0:e9:0a:51:2d:c4:b6:87: + 68:96:87:f8:9a:86:3c:6a:f1:01:ca:57:c4:07:e7: + b0:51 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Authority Key Identifier: + keyid:D6:4D:F9:39:60:6C:73:C3:22:F5:AD:30:0C:2F:A0:D5:CA:75:4A:2A + + X509v3 Subject Key Identifier: + A3:B3:47:2C:41:5E:9C:B2:27:97:57:14:A4:2E:BA:8C:93:E7:01:65 + X509v3 Subject Alternative Name: + DNS:example.com + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 CRL Distribution Points: + + Full Name: + URI:http://crl3.digicert.com/DigiCertTestSHA2IntermediateCA1.crl + + Full Name: + URI:http://crl4.digicert.com/DigiCertTestSHA2IntermediateCA1.crl + + X509v3 Certificate Policies: + Policy: 2.16.840.1.114412.1.1 + CPS: https://www.digicert.com/CPS + + Authority Information Access: + OCSP - URI:http://ocsp.digicert.com + CA Issuers - URI:http://cacerts.test.digicert.com/DigiCertTestSHA2IntermediateCA1.crt + + X509v3 Basic Constraints: critical + CA:FALSE + Signature Algorithm: sha256WithRSAEncryption + ae:d4:9c:8a:66:19:9e:7d:12:b7:05:c2:b6:33:b3:9c:a5:40: + 47:ab:34:8d:1b:0f:51:96:de:e9:46:5a:e4:16:10:43:56:bf: + fa:f8:64:f4:cb:53:39:5b:45:ca:7f:15:d9:59:25:21:23:c4: + 4d:dc:a7:f7:83:21:d2:3f:a8:0a:26:f4:ef:fa:1b:2b:7d:97: + 7e:28:f3:ca:cd:b2:c4:92:f3:92:27:7f:e0:f1:ac:d6:db:4c: + 10:8a:f8:6f:09:bb:b3:4f:19:06:aa:bb:74:1c:e0:51:42:f6: + 8c:7d:77:f7:80:a4:03:ab:a9:ae:ae:2b:89:17:af:2f:eb:f7: + 3d:61:7c:dd:e1:5d:d2:5a:c5:6a:f6:c8:92:4c:0a:b5:75:d1: + dd:39:f2:a7:a2:10:8c:6d:bf:ca:08:ad:b9:a9:df:e3:59:8f: + 64:16:3c:7e:8a:6e:27:fc:49:d7:06:f0:bd:94:15:f2:fd:0f: + 94:8a:b8:73:67:73:53:22:df:9d:36:e9:34:f9:2a:68:00:59: + 78:6d:2d:8f:a0:0f:13:af:bd:b3:aa:8c:37:c4:22:cf:23:fb: + 56:bc:4e:55:ae:3a:0a:e6:3e:b1:1a:22:71:7b:08:b8:00:41: + 14:26:f6:9b:9b:72:3f:eb:dc:dd:1b:db:a8:20:fd:54:75:ae: + 25:7f:80:e6 +``` + +In the next step, we'll configure your application to actually use this new +Certificate resource. + +## Exposing and securing your application + +Now that we have issued a Certificate, we can expose our application using a +Kubernetes Ingress resource. + +Create a file named `application-ingress.yaml` and save the following in it, +replacing `example.com` with your own domain name: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: frontend-ingress + namespace: demo +spec: + ingressClassName: nginx + tls: + - hosts: + - example.com + secretName: example-com-tls + rules: + - host: example.com + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: kuard + port: + number: 80 +``` + +You can then apply this resource with: + +```bash +$ kubectl apply -n demo -f application-ingress.yaml +``` + +Once this has been created, you should be able to visit your application at the +configured URL, here `example.com`! + +Navigate to the address in your web browser and you should see the certificate +obtained via Venafi being used to secure application traffic. diff --git a/content/v1.13-docs/tutorials/zerossl/zerossl.md b/content/v1.13-docs/tutorials/zerossl/zerossl.md new file mode 100644 index 0000000000..dd3e6e1fbf --- /dev/null +++ b/content/v1.13-docs/tutorials/zerossl/zerossl.md @@ -0,0 +1,180 @@ +--- +title: "Securing Ingresses with ZeroSSL" +linkTitle: "Securing Ingresses with ZeroSSL" +--- + +# The ZeroSSL + +This guide walks you through how to secure a Kubernetes [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) resource using the ZeroSSL Issuer type. + +The ZeroSSL just like Let's Encrypt and its competitors allows to create free 90 days certificates. All is need is to create account at https://zerossl.com/. After that go to developer section and generate `EAB Credentials for ACME Clients`. You will need it later. + + +`Please note!` \ +EAB credentials are not stored in your account, please make sure to note them somewhere. Each click on "Generate" will create a new set of credentials. Even if you create multiple credentials, all of them will remain functional. + + + +# Prerequisites + +- An AWS account +- kubectl installed +- Access to a publicly registered DNS zone +- Kubernetes cluster, you can use AWS EKS +- [ingress-nginx](https://kubernetes.github.io/ingress-nginx/) deployed and working inside cluster + + +# Tutorial scenario: + +## Installing cert-manager + +Make sure you use cert-manager `1.8.2+`/`1.7.3+`. See [link](https://github.com/cert-manager/cert-manager/pull/5226) for more details. + +Please walk through the installation guide and return to this step once you +have validated cert-manager is deployed correctly. Follow steps under [running on +Kubernetes](../../installation/helm.md) to install in k8s. + +In order to automatically switch to the ZeroSSL we recommend setting default shim by adding the following configuration to values file. + +```yaml +ingressShim: + defaultIssuerName: "zerossl-production" + defaultIssuerKind: "ClusterIssuer" + +installCRDs: true +``` + +Install it using helm: +``` +helm upgrade --install --namespace cert-manager --version v1.8.2 cert-manager jetstack/cert-manager -f values.yaml +``` + +## Configure your DNS records + +The best way to manage DNS using AWS is by using Route53. Create AWS account with permissions to modify Route53 rules. + +## EAB secret +Once you will get your credentials first step is to create seed with secrets. They are responsible for authenticating with your ZeroSSL account. + +```bash +$ kubectl create secret generic \ + zero-ssl-eabsecret \ + --namespace=cert-manager \ + --from-literal=secret='YOUR_ZEROSSL_EAB_HMAC_KEY' +``` + +### Another way of creating secret. + +Encode it in base64 first. +```bash +echo -n "YOUR_ZEROSSL_EAB_HMAC_KEY" | base64 -w 0 +``` + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: zero-ssl-eabsecret +data: + secret: YOUR_ENCODED_ZEROSSL_EAB_HMAC_KEY +``` +```bash +kubectl apply -f zero-ssl-eabsecret.yaml -n cert-manager +``` + +## Cluster issuer +Then we must create the `ZeroSSL` `ClusterIssuer`, let's call it `zerossl-production`. In our case we are using AWS. See pre-conditions to provision all required elements. + +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: zerossl-production +spec: + acme: + # ZeroSSL ACME server + server: https://acme.zerossl.com/v2/DV90 + email: dummy-email@yopmail.com + + # name of a secret used to store the ACME account private key + privateKeySecretRef: + name: zerossl-prod + + # for each cert-manager new EAB credencials are required + externalAccountBinding: + keyID: YOUR_ZEROSSL_EAB_KEY_ID + keySecretRef: + name: zero-ssl-eabsecret + key: secret + keyAlgorithm: HS256 + + # ACME DNS-01 provider configurations to verify domain + solvers: + - selector: {} + dns01: + route53: + region: us-west-2 + # optional if ambient credentials are available; see ambient credentials documentation + # see Route53 for >0 issue "letsencrypt.org" and change to >0 issue "sectigo.com" + accessKeyID: ACCESS_KEY_ID + secretAccessKeySecretRef: + name: route53-credentials-secret + key: secret-access-key + +``` + +### Then run: + +```bash +$ kubectl apply -n cert-manager -f zerossl-production.yaml +``` + +```bash +$ kubectl describe Clusterissuer zerossl-prod + +Status: + Acme: + Last Registered Email: dummy-email@yopmail.com + Uri: https://acme.zerossl.com/v2/DV90/account/tXXX_NwSv15rlS_XXXX + Conditions: + Last Transition Time: 2021-09-09T17:03:26Z + Message: The ACME account was registered with the ACME server + Reason: ACMEAccountRegistered + Status: True + Type: Ready +``` + +### Please note! +If this step failed and the ACME account is not registered please check if secrets in `zero-ssl-eabsecret` are correct. + +## Request a ingress certificate + + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: test-ingress + namespace: default +spec: + rules: + - host: test.example.com + tls: + - secretName: secret-tls + +``` + +Apply test-ingress: + +```bash +kubectl apply -f ingress.yaml +``` + +You are set! Check your ingress. +```bash +kubectl describe ingress test-ingress -n default +# check if tls is terminated using secret-tls + +openssl s_client -showcerts -connect test.example.com:443 +# verify server certificate and its chain +``` diff --git a/content/v1.13-docs/usage/README.md b/content/v1.13-docs/usage/README.md new file mode 100644 index 0000000000..f02af619dc --- /dev/null +++ b/content/v1.13-docs/usage/README.md @@ -0,0 +1,31 @@ +--- +title: Requesting Certificates +description: 'cert-manager usage: Overview' +--- + +Once an [`Issuer`](../configuration/README.md) has been configured, you're ready to issue your first certificate! + +There are several use cases and methods for requesting certificates through cert-manager: + +- [Certificate Resources](./certificate.md): The simplest and most common method for + requesting signed certificates. +- [Securing Ingress Resources](./ingress.md): A method to secure ingress resources + in your cluster. +- [Securing OpenFaaS functions](https://docs.openfaas.com/reference/ssl/kubernetes-with-cert-manager/): + Secure your OpenFaaS services using cert-manager. +- [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a + developer tool for developing Kubernetes applications which has first class + support for integrating cert-manager. +- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure + your Knative services with trusted HTTPS certificates. +- [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI + driver to provide unique keys and certificates that share the lifecycle of + pods. +- [Securing Istio Gateway](https://istio.io/docs/tasks/traffic-management/ingress/ingress-certmgr/): + Secure your Istio Gateway in Kubernetes using cert-manager. +- [Securing Istio Service Mesh](./istio.md): Using the cert-manager + [Istio](https://istio.io) integration, secure the mTLS PKI for each pod + through cert-manager managed certificates. +- [Policy for cert-manager certificates](./approver-policy.md): Manage + what cert-manager certificates are able to be signed or rejected through + custom resource defined policy. diff --git a/content/v1.13-docs/usage/approver-policy.md b/content/v1.13-docs/usage/approver-policy.md new file mode 100644 index 0000000000..b712d04b7f --- /dev/null +++ b/content/v1.13-docs/usage/approver-policy.md @@ -0,0 +1,14 @@ +--- +title: Policy for cert-manager certificates +description: 'cert-manager usage: approver-policy' +--- + +cert-manager [CertificateRequests](../concepts/certificaterequest.md) can be +rejected from being signed by using the [approval +API](../concepts/certificaterequest.md#approval). +[approver-policy](https://github.com/cert-manager/approver-policy) is a +cert-manager project that enables you to write policy to automatically manage +this approval mechanism. + +Please read the [project page](../projects/approver-policy/README.md) for more +information on how to install and use approver-policy. diff --git a/content/v1.13-docs/usage/certificate.md b/content/v1.13-docs/usage/certificate.md new file mode 100644 index 0000000000..a11dcd08b3 --- /dev/null +++ b/content/v1.13-docs/usage/certificate.md @@ -0,0 +1,372 @@ +--- +title: Certificate Resources +description: 'cert-manager usage: Certificates' +--- + +In cert-manager, the [`Certificate`](../concepts/certificate.md) resource +represents a human readable definition of a certificate request that is to be +honored by an issuer which is to be kept up-to-date. This is the usual way that +you will interact with cert-manager to request signed certificates. + +In order to issue any certificates, you'll need to configure an +[`Issuer`](../configuration/README.md) or [`ClusterIssuer`](../configuration/README.md) +resource first. + +## Creating Certificate Resources + +A `Certificate` resource specifies fields that are used to generate certificate +signing requests which are then fulfilled by the issuer type you have +referenced. `Certificates` specify which issuer they want to obtain the +certificate from by specifying the `certificate.spec.issuerRef` field. + +A `Certificate` resource, for the `example.com` and `www.example.com` DNS names, +`spiffe://cluster.local/ns/sandbox/sa/example` URI Subject Alternative Name, +that is valid for 90 days and renews 15 days before expiry is below. It contains +an exhaustive list of all options a `Certificate` resource may have however only +a subset of fields are required as labelled. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com + namespace: sandbox +spec: + # Secret names are always required. + secretName: example-com-tls + + # secretTemplate is optional. If set, these annotations and labels will be + # copied to the Secret named example-com-tls. These labels and annotations will + # be re-reconciled if the Certificate's secretTemplate changes. secretTemplate + # is also enforced, so relevant label and annotation changes on the Secret by a + # third party will be overwriten by cert-manager to match the secretTemplate. + secretTemplate: + annotations: + my-secret-annotation-1: "foo" + my-secret-annotation-2: "bar" + labels: + my-secret-label: foo + + duration: 2160h # 90d + renewBefore: 360h # 15d + subject: + organizations: + - jetstack + # The use of the common name field has been deprecated since 2000 and is + # discouraged from being used. + commonName: example.com + isCA: false + privateKey: + algorithm: RSA + encoding: PKCS1 + size: 2048 + usages: + - server auth + - client auth + # At least one of a DNS Name, URI, or IP address is required. + dnsNames: + - example.com + - www.example.com + uris: + - spiffe://cluster.local/ns/sandbox/sa/example + ipAddresses: + - 192.168.0.5 + # Issuer references are always required. + issuerRef: + name: ca-issuer + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: Issuer + # This is optional since cert-manager will default to this value however + # if you are using an external issuer, change this to that issuer group. + group: cert-manager.io +``` + +The signed certificate will be stored in a `Secret` resource named +`example-com-tls` in the same namespace as the `Certificate` once the issuer has +successfully issued the requested certificate. + +If `secretTemplate` is present, annotations and labels set in this property +will be copied over to `example-com-tls` secret. Both properties are optional. + +The `Certificate` will be issued using the issuer named `ca-issuer` in the +`sandbox` namespace (the same namespace as the `Certificate` resource). + +> Note: If you want to create an `Issuer` that can be referenced by +> `Certificate` resources in _all_ namespaces, you should create a +> [`ClusterIssuer`](../concepts/issuer.md#namespaces) resource and set the +> `certificate.spec.issuerRef.kind` field to `ClusterIssuer`. + +> Note: The `renewBefore` and `duration` fields must be specified using a [Go +> `time.Duration`](https://golang.org/pkg/time/#ParseDuration) string format, +> which does not allow the `d` (days) suffix. You must specify these values +> using `s`, `m`, and `h` suffixes instead. Failing to do so without installing +> the [`webhook component`](../concepts/webhook.md) can prevent cert-manager +> from functioning correctly +> [`#1269`](https://github.com/cert-manager/cert-manager/issues/1269). + +> Note: Take care when setting the `renewBefore` field to be very close to the +> `duration` as this can lead to a renewal loop, where the `Certificate` is always +> in the renewal period. Some `Issuers` set the `notBefore` field on their +> issued X.509 certificates before the issue time to fix clock-skew issues, +> leading to the working duration of a certificate to be less than the full +> duration of the certificate. For example, Let's Encrypt sets it to be one hour +> before issue time, so the actual _working duration_ of the certificate is 89 +> days, 23 hours (the _full duration_ remains 90 days). + +A full list of the fields supported on the Certificate resource can be found in +the [API reference documentation](../reference/api-docs.md#cert-manager.io/v1.CertificateSpec). + + +## X.509 key usages and extended key usages + +cert-manager supports requesting certificates that have a number of [custom key +usages](https://tools.ietf.org/html/rfc5280#section-4.2.1.3) and [extended key +usages](https://tools.ietf.org/html/rfc5280#section-4.2.1.12). Although +cert-manager will attempt to honor this request, some issuers will remove, add +defaults, or otherwise completely ignore the request. +The `CA` and `SelfSigned` `Issuer` will always return certificates matching the usages you have requested. + +Unless any number of usages has been set, cert-manager will set the default +requested usages of `digital signature`, `key encipherment`, and `server auth`. +cert-manager will not attempt to request a new certificate if the current +certificate does not match the current key usage set. + +An exhaustive list of supported key usages can be found in the [API reference +documentation](../reference/api-docs.md#cert-manager.io/v1.KeyUsage). + + +## Temporary Certificates while Issuing + +When requesting certificates [using the ingress-shim](./ingress.md), the +component `ingress-gce`, if used, requires that a temporary certificate is +present while waiting for the issuance of a signed certificate when serving. To +facilitate this, if the following annotation: + + ```yaml + cert-manager.io/issue-temporary-certificate: "true" + ``` + +is present on the certificate, a self-signed temporary certificate will be +present on the `Secret` until it is overwritten once the signed certificate has +been issued. + +Adding the following annotation on an ingress will automatically set "issue-temporary-certificate" on the certificate: + + ```yaml + acme.cert-manager.io/http01-edit-in-place: "true" + ``` + + +## Rotation of the private key + +By default, the private key won't be rotated automatically. Using the setting +`rotationPolicy: Always`, the private key Secret associated with a Certificate +object can be configured to be rotated as soon as an action triggers the +reissuance of the Certificate object (see +[Actions that will trigger a rotation of the private key](#actions-triggering-private-key-rotation) below). + +With `rotationPolicy: Always`, cert-manager waits until the Certificate +object is correctly signed before overwriting the `tls.key` file in the +Secret. + +With this setting, you can expect **no downtime** if your application can detect +changes to the mounted `tls.crt` and `tls.key` and reload them gracefully or +automatically restart. + +If your application only loads the private key and signed certificate once +at start up, the new certificate won't immediately be served by your +application, and you will want to either manually restart your pod with +`kubectl rollout restart`, or automate the action by running +[wave](https://github.com/wave-k8s/wave). Wave is a Secret controller that +makes sure deployments get restarted whenever a mounted Secret changes. + +
              + +Re-use of private keys + +Some issuers, like the built-in [Venafi +issuer](../configuration/venafi.md), may disallow re-using private keys. +If this is the case, you must explicitly configure the `rotationPolicy: +Always` setting for each of your Certificate objects accordingly. + +
              + +In the following example, the certificate has been set with +`rotationPolicy: Always`: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +spec: + secretName: my-cert-tls + privateKey: + rotationPolicy: Always # 🔰 Here. +``` + + +### Actions that will trigger a rotation of the private key + +Setting the `rotationPolicy: Always` won't rotate the private key immediately. +In order to rotate the private key, the certificate objects must be reissued. A +certificate object is reissued under the following circumstances: + +- when the X.509 certificate is nearing expiry, which is when the Certificate's + `status.renewalTime` is reached; +- when a change is made to one of the following fields on the Certificate's + spec: `commonName`, `dnsNames`, `ipAddresses`, `uris`, `emailAddresses`, + `subject`, `isCA`, `usages`, `duration` or `issuerRef`; +- when a reissuance is manually triggered with the following: + ```sh + cmctl renew cert-1 + ``` + Note that the above command requires [cmctl](../reference/cmctl.md#renew). + +
              + +**❌** Deleting the Secret resource associated with a Certificate resource is +**not a recommended solution** for manually rotating the private key. The +recommended way to manually rotate the private key is to trigger the reissuance +of the Certificate resource with the following command (requires +[`cmctl`](../reference/cmctl.md#renew)): + +```sh +cmctl renew cert-1 +``` + +
              + +### The `rotationPolicy` setting + +The possible values for `rotationPolicy` are: + +| Value | Description | +| ---------------------- | ------------------------------------------------------------- | +| `Never` (default) | cert-manager reuses the existing private key on each issuance | +| `Always` (recommended) | cert-manager regenerates a new private key on each issuance | + +With `rotationPolicy: Never`, a private key is only generated if one does not +already exist in the target Secret resource (using the `tls.key` key). All +further issuances will re-use this private key. This is the default in order to +maintain compatibility with previous releases. + +With `rotationPolicy: Always`, a new private key will be generated each time an +action triggers the reissuance of the certificate object (see [Actions that will +trigger a rotation of the private key](#actions-triggering-private-key-rotation) +above). Note that if the private key secret already exists when creating the +certificate object, the existing private key will not be used, since the +rotation mechanism also includes the initial issuance. + +
              + +👉 We recommend that you configure `rotationPolicy: Always` on your Certificate +resources. Rotating both the certificate and the private key simultaneously +prevents the risk of issuing a certificate with an exposed private key. Another +benefit to renewing the private key regularly is to let you be confident that +the private key rotation can be done in case of emergency. More generally, it is +a good practice to be rotating the keys as often as possible, reducing the risk +associated with compromised keys. + +
              + +## Cleaning up Secrets when Certificates are deleted + +By default, cert-manager does not delete the `Secret` resource containing the signed certificate when the corresponding `Certificate` resource is deleted. +This means that deleting a `Certificate` won't take down any services that are currently relying on that certificate, but the certificate will no longer be renewed. +The `Secret` needs to be manually deleted if it is no longer needed. + +If you would prefer the `Secret` to be deleted automatically when the `Certificate` is deleted, you need to configure your installation to pass the `--enable-certificate-owner-ref` flag to the controller. + +## Renewal + +cert-manager will automatically renew `Certificate`s. It will calculate _when_ to renew a `Certificate` based on the issued X.509 certificate's duration and a 'renewBefore' value which specifies _how long_ before expiry a certificate should be renewed. + +`spec.duration` and `spec.renewBefore` fields on a `Certificate` can be used to specify an X.509 certificate's duration and a 'renewBefore' value. Default value for `spec.duration` is 90 days. Some issuers might be configured to only issue certificates with a set duration, so the actual duration may be different. +Minimum value for `spec.duration` is 1 hour and minimum value for `spec.renewBefore` is 5 minutes. +It is also required that `spec.duration` > `spec.renewBefore`. + +Once an X.509 certificate has been issued, cert-manager will calculate the renewal time for the `Certificate`. By default this will be 2/3 through the X.509 certificate's duration. If `spec.renewBefore` has been set, it will be `spec.renewBefore` amount of time before expiry. cert-manager will set `Certificate`'s `status.RenewalTime` to the time when the renewal will be attempted. + +## Additional Certificate Output Formats + +
              + +⛔️ The additional certificate output formats feature is currently in an +_experimental_ alpha state, and is subject to breaking changes or complete +removal in future releases. This feature is only enabled by adding it to the +`--feature-gates` flag on the cert-manager controller and webhook components: + +```bash +--feature-gates=AdditionalCertificateOutputFormats=true +``` + +
              + +`additionalOutputFormats` is a field on the Certificate `spec` that allows +specifying additional supplementary formats of issued certificates and their +private key. There are currently two supported additional output formats: +`CombinedPEM` and `DER`. Both output formats can be specified on the same +Certificate. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +spec: + ... + secretName: my-cert-tls + additionalOutputFormats: + - type: CombinedPEM + - type: DER + +# Results in: + +apiVersion: v1 +kind: Secret +metadata: + name: my-cert-tls +type: kubernetes.io/tls +data: + ca.crt: + tls.key: + tls.crt: + tls-combined.pem: + key.der: +``` + +#### `CombinedPEM` + +The `CombinedPEM` type will create a new key entry in the resulting +Certificate's Secret `tls-combined.pem`. This entry will contain the PEM encoded +private key, followed by at least one new line character, followed by the PEM +encoded signed certificate chain- + +```text + + "\n" + +``` + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-cert-tls +type: kubernetes.io/tls +data: + tls-combined.pem: + ... +``` + +#### `DER` + +The `DER` type will create a new key entry in the resulting Certificate's Secret +`key.der`. This entry will contain the DER binary format of the private key. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-cert-tls +type: kubernetes.io/tls +data: + key.der: + ... +``` diff --git a/content/v1.13-docs/usage/csi.md b/content/v1.13-docs/usage/csi.md new file mode 100644 index 0000000000..5e1a6124b0 --- /dev/null +++ b/content/v1.13-docs/usage/csi.md @@ -0,0 +1,85 @@ +--- +title: CSI Driver +description: 'cert-manager usage: CSI driver' +--- + +## Enabling mTLS of Pods using the cert-manager CSI Driver + +A [Container Storage Interface (CSI) +driver](../projects/csi-driver.md) has been created to +facilitate mTLS of Pods running inside your cluster through use of cert-manager. +Using this driver will ensure that the private key and corresponding signed +certificate will be unique to each Pod and will be stored on disk to the node +that the Pod is scheduled to. The life cycle of the certificate key pair matches +that of the Pod meaning that they will be created at Pod creation, and destroyed +during termination. This driver also handles renewal on live certificates on the +fly. + +A [CSI +driver](https://github.com/container-storage-interface/spec/blob/master/spec.md) +is a storage plugin that is deployed into your Kubernetes cluster that can +honor volume requests specified on Pods, just like those enabled by default such as +the `Secret`, `ConfigMap`, or `hostPath` volume drivers. In the case of the cert-manager +CSI driver, it makes use of the ephemeral volume type, made beta as of +[`v1.16`](https://kubernetes.io/docs/concepts/storage/volumes/#csi-ephemeral-volumes) +and as such will only work from the Kubernetes version `v1.16`. An ephemeral +volumes means that the volume is created and destroyed as the Pod is created and +terminated, as well as specifying the volume attributes, without the need of a +`PersistentVolume`. This gives the feature of not only having unique +certificates and keys per Pod, where the private key never leaves the hosts +node, but that the desired certificate for that Pod template can be defined in +line with the deployment spec. + +> **Warning**: Use of the CSI driver is mostly intended for supporting a PKI of +> your cluster and facilitating mTLS, and as such, a private Certificate +> Authority issuer should be used - CA, Vault, and perhaps Venafi, or other +> external issuers. It is *not* recommended to use public Certificate +> Authorities, for example Let's Encrypt, which hold strict rate limits on the +> number of certificates that can be issued for a single domain. Like Pods, +> these certificate key pairs are designed to be non-immutable and can be +> created and destroyed at any time during normal operation. + +## How Does it Work? + +The CSI specification is a protocol and standard for building storage drivers +for container orchestration platforms with the intention that a single driver +may be ported across multiple platforms and outlines a consistent specification +to how drivers should behave from an infrastructure perspective. Since +cert-manager is designed to only be run with a Kubernetes cluster, so too does +the cert-manager CSI driver. + +The driver should be deployed as a +[DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) +which means a single instance of the driver may be run on each node. The driver +will not work when running multiple instances on a single node. The set of nodes +that the driver runs on can be restricted using the +[`nodeSelector`](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/) +in its Pod template. + +When a Pod is scheduled to a node with a cert-manager CSI volume specified, the +[`Kubelet`](https://kubernetes.io/docs/concepts/overview/components/#kubelet) +running on that node will send a `NodePublishVolume` call to the driver on that +node, containing that Pods information as well as the attributes detailed from +the in-line volume attributes. From this, the driver will generate a private key +as well as a certificate request based upon that key using information built +from the volume attributes. The driver will create a `CertificateRequest` +resource in the same namespace in the Pod that, if valid, cert-manager will +return a signed certificate. + +The resulting signed certificate and generated key will be written to that +node's file system to be mounted to the Pods file system. Since the driver needs +access to the nodes file system it must be made privileged. Once mounted, the +Pod will begin execution with the unique private key and certificate available in +its file system, as defined by its mount path. + +By default, the driver will keep track of certificates created in order to +monitor when they should be marked for renewal. When this happens, the driver +will request for a new signed certificate, and when successful, will simply +overwrite the existing certificate in path. + +When the Pod is marked for termination, the `NodeUnpublishVolume` call is made +to the node's driver which in turn destroys the certificate and key from the +nodes file system. + +The CSI driver is able to recover its full state in the event the its Pod being +terminated. diff --git a/content/v1.13-docs/usage/gateway.md b/content/v1.13-docs/usage/gateway.md new file mode 100644 index 0000000000..45f8fe62b7 --- /dev/null +++ b/content/v1.13-docs/usage/gateway.md @@ -0,0 +1,438 @@ +--- +title: Securing gateway.networking.k8s.io Gateway Resources +description: 'cert-manager usage: Kubernetes Gateways' +--- + +**FEATURE STATE**: cert-manager 1.5 [alpha] + +
              + +📌 This page focuses on automatically creating Certificate resources by +annotating Kubernetes Gateway resource. If you are looking for using an ACME Issuer along +with HTTP-01 challenges using the Kubernetes Gateway API, see [ACME +HTTP-01](../configuration/acme/http01/README.md). + +
              + +
              + +🚧 cert-manager 1.8+ is tested with v1alpha2 Kubernetes Gateway API. It should also work +with v1beta1 because of resource conversion, but has not been tested with it. + +
              + +cert-manager can generate TLS certificates for Gateway resources. This is +configured by adding annotations to a Gateway and is similar to the process for +[Securing Ingress Resources](../usage/ingress.md). + +The Gateway resource is part of the [Gateway API][gwapi], a set of CRDs that you +install on your Kubernetes cluster and which provide various improvements over +the Ingress API. + +[gwapi]: https://gateway-api.sigs.k8s.io + +The Gateway resource holds the TLS configuration, as illustrated in the +following diagram (source: https://gateway-api.sigs.k8s.io): + +![Gateway vs. HTTPRoute](/images/gateway-roles.png) + +
              + +📌 This feature requires the installation of the [Gateway API bundle](https://gateway-api.sigs.k8s.io/guides/#installing-a-gateway-controller) and passing a +feature flag to the cert-manager controller. + +To install v1.5.1 Gateway API bundle (Gateway CRDs and webhook), run the following command: + +```sh +kubectl apply -f "https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.5.1/standard-install.yaml" +``` + +To enable the feature in cert-manager, turn on the `GatewayAPI` feature gate: + +- If you are using Helm: + + ```sh + helm upgrade --install cert-manager jetstack/cert-manager --namespace cert-manager \ + --set "extraArgs={--feature-gates=ExperimentalGatewayAPISupport=true}" + ``` + +- If you are using the raw cert-manager manifests, add the following flag to the + cert-manager controller Deployment: + + ```yaml + args: + - --feature-gates=ExperimentalGatewayAPISupport=true + ``` + +The Gateway API CRDs should either be installed before cert-manager starts or +the cert-manager Deployment should be restarted after installing the Gateway API +CRDs. This is important because some of the cert-manager components only perform +the Gateway API check on startup. You can restart cert-manager with the +following command: + +```sh +kubectl rollout restart deployment cert-manager -n cert-manager +``` + +
              + +The annotations `cert-manager.io/issuer` or `cert-manager.io/cluster-issuer` +tell cert-manager to create a Certificate for a Gateway. For example, the +following Gateway will trigger the creation of a Certificate with the name +`example-com-tls`: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: Gateway +metadata: + name: example + annotations: + cert-manager.io/issuer: foo +spec: + gatewayClassName: foo + listeners: + - name: http + hostname: example.com + port: 443 + protocol: HTTPS + allowedRoutes: + namespaces: + from: All + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls +``` + +A few moments later, cert-manager will create a Certificate. The Certificate is +named after the Secret name `example-com-tls`. The `dnsNames` field is set with +the `hostname` field from the Gateway spec. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-tls +spec: + issuerRef: + name: my-issuer + kind: Issuer + group: cert-manager.io + dnsNames: + - example.com # ✅ Copied from the `hostname` field. + secretName: example-com-tls +``` + +
              + +🚧 this mechanism can only be used to create Secrets in the same namespace as the `Gateway`, see [`cert-manager#5610`](https://github.com/cert-manager/cert-manager/issues/5610) + +
              + +## Use cases + +### Generate TLS certs for selected TLS blocks + +cert-manager skips any listener block that cannot be used for generating a +Certificate. For a listener block to be used for creating a Certificate, it must +meet the following requirements: + +| Field | Requirement | +|--------------------------------|-------------------------------------------------------------| +| `tls.hostname` | Must not be empty. | +| `tls.mode` | Must be set to `Terminate`. `Passthrough` is not supported. | +| `tls.certificateRef.name` | Cannot be left empty. | +| `tls.certificateRef.kind` | If specified, must be set to `Secret`. | +| `tls.certificateRef.group` | If specified, must be set to `core`. | +| `tls.certificateRef.namespace` | If specified, must be the same as the `Gateway`. | + +In the following example, the first four listener blocks will not be used to +generate Certificate resources: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: Gateway +metadata: + name: my-gateway + namespace: default + annotations: + cert-manager.io/issuer: my-issuer +spec: + listeners: + # ❌ Missing "tls" block, the following listener is skipped. + - hostname: example.com + + # ❌ Missing "hostname", the following listener is skipped. + - tls: + certificateRefs: + - name: example-com-tls + kind: Secret" + group: core + + # ❌ "mode: Passthrough" is not supported, the following listener is skipped. + - hostname: example.com + tls: + mode: Passthrough + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + + # ❌ Cross-namespace secret references are not supported, the following listener is skipped. + - hostname: foo.example.com + port: 443 + protocol: HTTPS + allowedRoutes: + namespaces: + from: All + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + namespace: other-namespace + + # ✅ The following listener is valid. + - hostname: foo.example.com # ✅ Required. + port: 443 + protocol: HTTPS + allowedRoutes: + namespaces: + from: All + tls: + mode: Terminate # ✅ Required. "Terminate" is the only supported mode. + certificateRefs: + - name: example-com-tls # ✅ Required. + kind: Secret # ✅ Required. "Secret" is the only valid value. + group: core # ✅ Required. "core" is the only valid value. +``` + +cert-manager has skipped over the first four listener blocks and has created a +single Certificate named `example-com-tls` for the last listener block: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-tls +spec: + issuerRef: + name: my-issuer + kind: Issuer + group: cert-manager.io + dnsNames: + - foo.example.com + secretName: example-com-tls +``` + +### Two listeners with the same Secret name + +The same Secret name can be re-used in multiple TLS blocks, regardless of the +hostname. Let us imagine that you have these two listeners: + +```yaml +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: Gateway +metadata: + name: example + annotations: + cert-manager.io/issuer: my-issuer +spec: + gatewayClassName: foo + listeners: + # Listener 1. + - hostname: example.com + port: 443 + protocol: HTTPS + routes: + kind: HTTPRoute + parentRefs: + - name: example + kind: Gateway + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + + # Listener 2: Same Secret name as Listener 1, with a different hostname. + - hostname: *.example.com + port: 443 + protocol: HTTPS + routes: + kind: HTTPRoute + parentRefs: + - name: example + kind: Gateway + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + + # Listener 3: also same Secret name, except the hostname is also the same. + - hostname: *.example.com + port: 8443 + protocol: HTTPS + routes: + kind: HTTPRoute + parentRefs: + - name: example + kind: Gateway + tls: + mode: Terminate + certificateRefs: + - name: example-com-tls + kind: Secret + group: core + + # Listener 4: different Secret name. + - hostname: site.org + port: 443 + protocol: HTTPS + routes: + kind: HTTPRoute + parentRefs: + - name: example + kind: Gateway + tls: + mode: Terminate + certificateRefs: + - name: site-org-tls + kind: Secret + group: core +``` + +cert-manager will create two Certificates since two Secret names are used: +`example-com-tls` and `site-org-tls`. Note the Certificate's `dnsNames` contains +a single occurrence of `*.example.com ` for both listener 2 and 3 (the +`hostname` values are de-duplicated). + +The two Certificates look like this: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: example-com-tls +spec: + issuerRef: + name: my-issuer + kind: Issuer + group: cert-manager.io + dnsNames: + - example.com # From listener 1. + - *.example.com # From listener 2 and 3. + secretName: example-com-tls +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: site-org-tls +spec: + issuerRef: + name: my-issuer + kind: Issuer + group: cert-manager.io + dnsNames: + - site.org # From listener 4. + secretName: site-org-tls +``` + +## Supported Annotations + +If you are migrating to Gateway resources from Ingress resources, be aware that +there are some differences between [the annotations for Ingress resources](./ingress.md#supported-annotations) +versus the annotations for Gateway resources. + +The Gateway resource supports the following annotations for generating +Certificate resources: + +- `cert-manager.io/issuer`: the name of an Issuer to acquire the certificate + required for this Gateway. The Issuer _must_ be in the same namespace as the + Gateway resource. + +- `cert-manager.io/cluster-issuer`: the name of a ClusterIssuer to acquire the + Certificate required for this Gateway. It does not matter which namespace your + Gateway resides, as `ClusterIssuers` are non-namespaced resources. + +- `cert-manager.io/issuer-kind`: the kind of the external issuer resource, for + example `AWSPCACIssuer`. This is only necessary for out-of-tree issuers. + +- `cert-manager.io/issuer-group`: the API group of the external issuer + controller, for example `awspca.cert-manager.io`. This is only necessary for + out-of-tree issuers. + +- `cert-manager.io/common-name`: (optional) this annotation allows you to + configure `spec.commonName` for the Certificate to be generated. + +- `cert-manager.io/email-sans`: (optional) this annotation allows you to + configure `spec.emailAddresses` field for the Certificate to be generated. + Supports comma-separated values e.g. "me@example.com,you@example.com" + +- `cert-manager.io/subject-organizations`: (optional) this annotation allows you to + configure `spec.subject.organizations` field for the Certificate to be generated. + Supports comma-separated values e.g. "Company 1,Company 2" + +- `cert-manager.io/subject-organizationalunits`: (optional) this annotation allows you to + configure `spec.subject.organizationalUnits` field for the Certificate to be generated. + Supports comma-separated values e.g. "IT Services,Cloud Services" + +- `cert-manager.io/subject-countries`: (optional) this annotation allows you to + configure `spec.subject.countries` field for the Certificate to be generated. + Supports comma-separated values e.g. "Country 1,Country 2" + +- `cert-manager.io/subject-provinces`: (optional) this annotation allows you to + configure `spec.subject.provinces` field for the Certificate to be generated. + Supports comma-separated values e.g. "Province 1,Province 2" + +- `cert-manager.io/subject-localities`: (optional) this annotation allows you to + configure `spec.subject.localities` field for the Certificate to be generated. + Supports comma-separated values e.g. "City 1,City 2" + +- `cert-manager.io/subject-postalcodes`: (optional) this annotation allows you to + configure `spec.subject.postalCodes` field for the Certificate to be generated. + Supports comma-separated values e.g. "123ABC,456DEF" + +- `cert-manager.io/subject-streetaddresses`: (optional) this annotation allows you to + configure `spec.subject.streetAddresses` field for the Certificate to be generated. + Supports comma-separated values e.g. "123 Example St,456 Other Blvd" + +- `cert-manager.io/subject-serialnumber`: (optional) this annotation allows you to + configure `spec.subject.serialNumber` field for the Certificate to be generated. + Supports comma-separated values e.g. "10978342379280287615,1111144445555522228888" + +- ` cert-manager.io/duration`: (optional) this annotation allows you to + configure `spec.duration` field for the Certificate to be generated. + +- `cert-manager.io/renew-before`: (optional) this annotation allows you to + configure `spec.renewBefore` field for the Certificate to be generated. + +- `cert-manager.io/usages`: (optional) this annotation allows you to configure + `spec.usages` field for the Certificate to be generated. Pass a string with + comma-separated values i.e "key agreement,digital signature, server auth" + +- `cert-manager.io/revision-history-limit`: (optional) this annotation allows you to + configure `spec.revisionHistoryLimit` field to limit the number of CertificateRequests to be kept for a Certificate. + Minimum value is 1. If unset all CertificateRequests will be kept. + +- `cert-manager.io/private-key-algorithm`: (optional) this annotation allows you to + configure `spec.privateKey.algorithm` field to set the algorithm for private key generation for a Certificate. + Valid values are `RSA`, `ECDSA` and `Ed25519`. If unset an algorithm `RSA` will be used. + +- `cert-manager.io/private-key-encoding`: (optional) this annotation allows you to + configure `spec.privateKey.encoding` field to set the encoding for private key generation for a Certificate. + Valid values are `PKCS1` and `PKCS8`. If unset an algorithm `PKCS1` will be used. + +- `cert-manager.io/private-key-size`: (optional) this annotation allows you to + configure `spec.privateKey.size` field to set the size of the private key for a Certificate. + If algorithm is set to `RSA`, valid values are `2048`, `4096` or `8192`, and will default to `2048` if not specified. + If algorithm is set to `ECDSA`, valid values are `256`, `384` or `521`, and will default to `256` if not specified. + If algorithm is set to `Ed25519`, size is ignored. + +- `cert-manager.io/private-key-rotation-policy`: (optional) this annotation allows you to + configure `spec.privateKey.rotationPolicy` field to set the rotation policy of the private key for a Certificate. + Valid values are `Never` and `Always`. If unset a rotation policy `Never` will be used. diff --git a/content/v1.13-docs/usage/ingress.md b/content/v1.13-docs/usage/ingress.md new file mode 100644 index 0000000000..26ff55751e --- /dev/null +++ b/content/v1.13-docs/usage/ingress.md @@ -0,0 +1,215 @@ +--- +title: Securing Ingress Resources +description: 'cert-manager usage: Kubernetes Ingress' +--- + +A common use-case for cert-manager is requesting TLS signed certificates to +secure your ingress resources. This can be done by simply adding annotations to +your `Ingress` resources and cert-manager will facilitate creating the +`Certificate` resource for you. A small sub-component of cert-manager, +ingress-shim, is responsible for this. + +## How It Works + +The sub-component ingress-shim watches `Ingress` resources across your cluster. +If it observes an `Ingress` with annotations described in the [Supported +Annotations](#supported-annotations) section, it will ensure a `Certificate` +resource with the name provided in the `tls.secretName` field and configured as +described on the `Ingress` exists in the `Ingress`'s namespace. For example: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + # add an annotation indicating the issuer to use. + cert-manager.io/cluster-issuer: nameOfClusterIssuer + name: myIngress + namespace: myIngress +spec: + rules: + - host: example.com + http: + paths: + - pathType: Prefix + path: / + backend: + service: + name: myservice + port: + number: 80 + tls: # < placing a host in the TLS config will determine what ends up in the cert's subjectAltNames + - hosts: + - example.com + secretName: myingress-cert # < cert-manager will store the created certificate in this secret. +``` + +## Supported Annotations + +You can specify the following annotations on Ingress resources in order to +trigger Certificate resources to be automatically created: + +- `cert-manager.io/issuer`: the name of the issuer that should issue the certificate + required for this Ingress. + + > ⚠️ This annotation does _not_ assume a namespace scoped issuer. It will + default to cert-manager.io Issuer, however in case of external issuer types, + this should be used for both namespaced and cluster scoped issuer types. + + > ⚠️ If a namespace scoped issuer is used then the issuer *must* be in + the same namespace as the Ingress resource. + +- `cert-manager.io/cluster-issuer`: the name of a cert-manager.io ClusterIssuer + to acquire the certificate required for this Ingress. It does not matter which + namespace your Ingress resides, as ClusterIssuers are non-namespaced + resources. + + > ⚠️ This annotation is a shortcut to refer to to + cert-manager.io ClusterIssuer without having to specify group and kind. It is + _not_ intended to be used to specify an external cluster-scoped issuer- please + use `cert-manager.io/issuer` annotation for those. + +- `cert-manager.io/issuer-kind`: the kind of the external issuer resource, for + example `AWSPCAIssuer`. This is only necessary for out-of-tree issuers. + +- `cert-manager.io/issuer-group`: the API group of the external issuer + controller, for example `awspca.cert-manager.io`. This is only necessary for + out-of-tree issuers. + +- `kubernetes.io/tls-acme: "true"`: this annotation requires additional + configuration of the ingress-shim [see below](#optional-configuration). + Namely, a default Issuer must be specified as arguments to the ingress-shim + container. + +- `acme.cert-manager.io/http01-ingress-class`: this annotation allows you to + configure the ingress class that will be used to solve challenges for this + ingress. Customizing this is useful when you are trying to secure internal + services, and need to solve challenges using a different ingress class to that + of the ingress. If not specified and the `acme-http01-edit-in-place` annotation + is not set, this defaults to the ingress class defined in the Issuer resource. + +- `acme.cert-manager.io/http01-edit-in-place: "true"`: this controls whether the + ingress is modified 'in-place', or a new one is created specifically for the + HTTP01 challenge. If present, and set to "true", the existing ingress will be + modified. Any other value, or the absence of the annotation assumes "false". + This annotation will also add the annotation + `"cert-manager.io/issue-temporary-certificate": "true"` onto created + certificates which will cause a [temporary + certificate](./certificate.md#temporary-certificates-whilst-issuing) to be set + on the resulting Secret until the final signed certificate has been returned. + This is useful for keeping compatibility with the `ingress-gce` component. + +- `cert-manager.io/common-name`: (optional) this annotation allows you to + configure `spec.commonName` for the Certificate to be generated. + +- `cert-manager.io/email-sans`: (optional) this annotation allows you to + configure `spec.emailAddresses` field for the Certificate to be generated. + Supports comma-separated values e.g. "me@example.com,you@example.com" + +- `cert-manager.io/subject-organizations`: (optional) this annotation allows you to + configure `spec.subject.organizations` field for the Certificate to be generated. + Supports comma-separated values e.g. "Company 1,Company 2" + +- `cert-manager.io/subject-organizationalunits`: (optional) this annotation allows you to + configure `spec.subject.organizationalUnits` field for the Certificate to be generated. + Supports comma-separated values e.g. "IT Services,Cloud Services" + +- `cert-manager.io/subject-countries`: (optional) this annotation allows you to + configure `spec.subject.countries` field for the Certificate to be generated. + Supports comma-separated values e.g. "Country 1,Country 2" + +- `cert-manager.io/subject-provinces`: (optional) this annotation allows you to + configure `spec.subject.provinces` field for the Certificate to be generated. + Supports comma-separated values e.g. "Province 1,Province 2" + +- `cert-manager.io/subject-localities`: (optional) this annotation allows you to + configure `spec.subject.localities` field for the Certificate to be generated. + Supports comma-separated values e.g. "City 1,City 2" + +- `cert-manager.io/subject-postalcodes`: (optional) this annotation allows you to + configure `spec.subject.postalCodes` field for the Certificate to be generated. + Supports comma-separated values e.g. "123ABC,456DEF" + +- `cert-manager.io/subject-streetaddresses`: (optional) this annotation allows you to + configure `spec.subject.streetAddresses` field for the Certificate to be generated. + Supports comma-separated values e.g. "123 Example St,456 Other Blvd" + +- `cert-manager.io/subject-serialnumber`: (optional) this annotation allows you to + configure `spec.subject.serialNumber` field for the Certificate to be generated. + Supports comma-separated values e.g. "10978342379280287615,1111144445555522228888" + +- ` cert-manager.io/duration`: (optional) this annotation allows you to + configure `spec.duration` field for the Certificate to be generated. + +- `cert-manager.io/renew-before`: (optional) this annotation allows you to + configure `spec.renewBefore` field for the Certificate to be generated. + +- `cert-manager.io/usages`: (optional) this annotation allows you to configure + `spec.usages` field for the Certificate to be generated. Pass a string with + comma-separated values i.e "key agreement,digital signature, server auth" + +- `cert-manager.io/revision-history-limit`: (optional) this annotation allows you to + configure `spec.revisionHistoryLimit` field to limit the number of CertificateRequests to be kept for a Certificate. + Minimum value is 1. If unset all CertificateRequests will be kept. + +- `cert-manager.io/private-key-algorithm`: (optional) this annotation allows you to + configure `spec.privateKey.algorithm` field to set the algorithm for private key generation for a Certificate. + Valid values are `RSA`, `ECDSA` and `Ed25519`. If unset an algorithm `RSA` will be used. + +- `cert-manager.io/private-key-encoding`: (optional) this annotation allows you to + configure `spec.privateKey.encoding` field to set the encoding for private key generation for a Certificate. + Valid values are `PKCS1` and `PKCS8`. If unset an algorithm `PKCS1` will be used. + +- `cert-manager.io/private-key-size`: (optional) this annotation allows you to + configure `spec.privateKey.size` field to set the size of the private key for a Certificate. + If algorithm is set to `RSA`, valid values are `2048`, `4096` or `8192`, and will default to `2048` if not specified. + If algorithm is set to `ECDSA`, valid values are `256`, `384` or `521`, and will default to `256` if not specified. + If algorithm is set to `Ed25519`, size is ignored. + +- `cert-manager.io/private-key-rotation-policy`: (optional) this annotation allows you to + configure `spec.privateKey.rotationPolicy` field to set the rotation policy of the private key for a Certificate. + Valid values are `Never` and `Always`. If unset a rotation policy `Never` will be used. + +## Generate multiple certificates with multiple ingresses + +If you need to generate certificates from multiple ingresses make sure it has the issuer annotation. +Besides the annotation, it is necessary that each ingress possess a unique `tls.secretName` + +## Optional Configuration + +The ingress-shim sub-component is deployed automatically as part of +installation. + +If you would like to use the old +[kube-lego](https://github.com/jetstack/kube-lego) `kubernetes.io/tls-acme: +"true"` annotation for fully automated TLS, you will need to configure a default +`Issuer` when deploying cert-manager. This can be done by adding the following +`--set` when deploying using Helm: + +```bash + --set ingressShim.defaultIssuerName=letsencrypt-prod \ + --set ingressShim.defaultIssuerKind=ClusterIssuer \ + --set ingressShim.defaultIssuerGroup=cert-manager.io +``` + +Or by adding the following arguments to the cert-manager deployment +`podTemplate` container arguments. + +``` + - --default-issuer-name=letsencrypt-prod + - --default-issuer-kind=ClusterIssuer + - --default-issuer-group=cert-manager.io +``` + +In the above example, cert-manager will create `Certificate` resources that +reference the `ClusterIssuer` `letsencrypt-prod` for all Ingresses that have a +`kubernetes.io/tls-acme: "true"` annotation. + +Issuers configured via annotations have a preference over the default issuer. If a default issuer is configured via CLI flags and a `cert-manager.io/cluster-issuer` or `cert-manager.io/issuer` annotation also has been added to an Ingress, the created `Certificate` will refer to the issuer configured via annotation. + +For more information on deploying cert-manager, read the [installation +guide](../installation/README.md). + +## Troubleshooting + +If you do not see a `Certificate` resource being created after applying the ingress-shim annotations check that at least `cert-manager.io/issuer` or `cert-manager.io/cluster-issuer` is set. If you want to use `kubernetes.io/tls-acme: "true"` make sure to have checked all steps above and you might want to look for errors in the cert-manager pod logs if not resolved. diff --git a/content/v1.13-docs/usage/istio.md b/content/v1.13-docs/usage/istio.md new file mode 100644 index 0000000000..86149ae22b --- /dev/null +++ b/content/v1.13-docs/usage/istio.md @@ -0,0 +1,17 @@ +--- +title: Securing Istio Service Mesh +description: 'cert-manager usage: Istio and istio-csr' +--- + +cert-manager can be integrated with [Istio](https://istio.io) using the project +[istio-csr](https://github.com/cert-manager/istio-csr). istio-csr will deploy an +agent that is responsible for receiving certificate signing requests for all +members of the Istio mesh, and signing them through cert-manager. + +[istio-csr](https://github.com/cert-manager/istio-csr) will sign all control +plane and workload certificates via your chosen cert-manager Issuer. + +--- + +Please follow the instructions for installing and using istio-csr on the +[project page](../projects/istio-csr.md). diff --git a/content/v1.13-docs/usage/kube-csr.md b/content/v1.13-docs/usage/kube-csr.md new file mode 100644 index 0000000000..f6abb93f74 --- /dev/null +++ b/content/v1.13-docs/usage/kube-csr.md @@ -0,0 +1,166 @@ +--- +title: Kubernetes CertificateSigningRequests +description: 'cert-manager usage: Kubernetes CertificateSigningRequest resources' +--- + +Kubernetes has an in-built +[CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) +resource. This resource is similar to the cert-manager +[CertificateRequest](../concepts/certificaterequest.md) in that it is used to +request an X.509 signed certificate from a referenced Certificate Authority +(CA). + +Using this resource may be useful for users who are using an application that +supports this resource, but not the cert-manager CertificateRequest resource, +and they still wish for certificates to be signed through cert-manager. + +CertificateSigningRequests reference a `SignerName` or signer as the entity it +wishes to sign its request from. For cert-manager, a signer can be mapped to +either an [Issuer or ClusterIssuer](../configuration/README.md). + +#### Feature State + +This feature is currently in an _experimental_ state, and its behavior is +subject to change in further releases. + +
              + +⛔️ This feature is only enabled by adding it to the `--feature-gates` flag on +the cert-manager controller: + +```bash +--feature-gates=ExperimentalCertificateSigningRequestControllers=true +``` + +Which can be added using Helm: + +```bash +$ helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --set featureGates="ExperimentalCertificateSigningRequestControllers=true" \ + # --set installCRDs=true +``` + +> Note: cert-manager supports signing CertificateSigningRequests +> using all [internal Issuers](../configuration/README.md). + +> Note: cert-manager _does not_ automatically approve CertificateSigningRequests +> that reference a cert-manager [Issuer](../configuration/README.md). Please refer to +> the [Kubernetes documentation](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#request-signing-process) +> for the request process of CertificateSigningRequests. + + +
              + + +## Signer Name + +CertificateSigningRequests contain a +[`spec.signerName`](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#request-signing-process) +field to reference a CA to sign the request. cert-manager Issuers or +ClusterIssuers are referenced in the following form: + +``` +.cert-manager.io/. +``` + +For example, a namespaced Issuer in the namespace `sandbox` with the name +`my-issuer` would be referenced via: + +```yaml + signerName: issuers.cert-manager.io/sandbox.my-issuer +``` + +A ClusterIssuer with the name `my-cluster-issuer` would be referenced via: + +```yaml + signerName: clusterissuers.cert-manager.io/my-cluster-issuer +``` + +### Referencing Namespaced Issuers + +Unlike CertificateRequests, CertificateSigningRequests are cluster scoped +resources. To prevent users from requesting certificates from a namespaced +Issuer in a namespace that they otherwise would not have access to, cert-manager +performs a +[SubjectAccessReview](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#checking-api-access). +This review ensures that the requesting user has the permission to `reference` +the `signers` resource in the given namespace. The name should be either the +name of the Issuer, or `"*"` to reference all Issuers in that namespace. + +An example Role to give permissions to reference Issuers in the `sandbox` +namespace would look like the following: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cert-manager-referencer:my-issuer + namespace: sandbox +rules: +- apiGroups: ["cert-manager.io"] + resources: ["signers"] + verbs: ["reference"] + resourceNames: + - "my-issuer" # To give permission to _only_ reference Issuers with the name 'my-issuer' + - "*" # To give permission to reference Issuers with any name in this namespace +``` + +## Annotations + +To keep feature parity with CertificateRequests, annotations are used to store +values that do not exist as `spec` or `status` fields on the +CertificateSigningRequest resource. These fields are either set by the +_requester_ or by the _signer_ as labelled below. + +Requester annotations: + +- `experimental.cert-manager.io/request-duration`: **Set by the requester**. Accepts + a [Go time duration](https://golang.org/pkg/time/#ParseDuration) string + specifying the requested certificate duration. Defaults to 90 days. Some + signers such as Venafi or ACME typically _do not_ allow requesting a + duration. + +- `experimental.cert-manager.io/request-is-ca`: **Set by the requester**. If set to + `"true"`, will request for a CA certificate. + +- `experimental.cert-manager.io/private-key-secret-name`: **Set by the + requester**. Required only for the SelfSigned signer. Used to reference a + Secret which contains the PEM encoded private key of the requester's X.509 + certificate signing request at key `tls.key`. Used to sign the requester's + request. + +- `venafi.experimental.cert-manager.io/custom-fields`: **Set by the + requester**. Optional for only the Venafi signer. Used for adding custom + fields to the Venafi request. This will only work with Venafi TPP `v19.3` + and higher. The value is a JSON array with objects containing the name and + value keys, for example: + ``` + venafi.experimental.cert-manager.io/custom-fields: |- + [ + {"name": "field-name", "value": "field value"}, + {"name": "field-name-2", "value": "field value 2"} + ] + ``` + +Signer annotations: + +- `venafi.experimental.cert-manager.io/pickup-id`: **Set by the signer**. Only + used for the Venafi signer. Used to record the Venafi Pickup ID of a + certificate signing request that has been submitted to the Venafi API for + collection during issuance. + +## Usage + +CertificateSigningRequests can be manually created using +[cmctl](../reference/cmctl.md#experimental). +This command takes a manifest file containing a +[Certificate](../usage/certificate.md) resource as input. This generates a +private key and creates a CertificateSigningRequest. CertificateSigningRequests +are not approved by default, so you will likely need to approve it manually: + +```bash +$ kubectl certificate approve +``` diff --git a/content/v1.13-docs/usage/prometheus-metrics.md b/content/v1.13-docs/usage/prometheus-metrics.md new file mode 100644 index 0000000000..968ce2077d --- /dev/null +++ b/content/v1.13-docs/usage/prometheus-metrics.md @@ -0,0 +1,69 @@ +--- +title: Prometheus Metrics +description: 'cert-manager usage: Prometheus metrics' +--- + +To help with operations and insights into cert-manager activities, cert-manager exposes metrics in the [Prometheus](https://prometheus.io/) format from the controller component. These are available at the standard `/metrics` path of the controller component's configured HTTP port. + +## Scraping Metrics + +How metrics are scraped will depend how you're operating your Prometheus server(s). These examples presume the [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator) is being used to run Prometheus, and configure Pod or Service Monitor CRDs. + +### Helm + +If you're deploying cert-manager with helm, a `ServiceMonitor` resource can be configured. This configuration should enable metric scraping, and the configuration can be further tweaked as described in the [Helm configuration documentation](https://github.com/cert-manager/cert-manager/blob/master/deploy/charts/cert-manager/README.template.md#configuration). + +```yaml +prometheus: + enabled: true + servicemonitor: + enabled: true +``` + +### Regular Manifests + +If you're not using helm to deploy cert-manager and instead using the provided regular YAML manifests, this example `PodMonitor` and deployment patch should be all you need to start ingesting cert-manager metrics. + +1. [Apply the following patch](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/#use-a-strategic-merge-patch-to-update-a-deployment) to your cert-manager deployment + +```yaml +spec: + template: + spec: + containers: + - name: cert-manager-controller + ports: + - containerPort: 9402 + name: http + protocol: TCP +``` + +2. Create the following `PodMonitor` + +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: cert-manager + namespace: cert-manager + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" +spec: + jobLabel: app.kubernetes.io/name + selector: + matchLabels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + podMetricsEndpoints: + - port: http + honorLabels: true +``` + +## Monitoring Mixin + +Monitoring mixins are a way to bundle common alerts, rules, and dashboards for an application in a configurable and extensible way, using the Jsonnet data templating language. A cert-manager monitoring mixin can be found here https://gitlab.com/uneeq-oss/cert-manager-mixin. Documentation on usage can be found with the `cert-manager-mixin` project. diff --git a/scripts/gendocs/generate-new-import-path-docs b/scripts/gendocs/generate-new-import-path-docs index 09620e5dc4..1062e059ea 100755 --- a/scripts/gendocs/generate-new-import-path-docs +++ b/scripts/gendocs/generate-new-import-path-docs @@ -148,14 +148,15 @@ EOF # This script is _only_ for generating docs for versions of cert-manager with the # github.com/cert-manager/cert-manager import path! -LATEST_VERSION="v1.12-docs" +LATEST_VERSION="v1.13-docs" #genversionwithcli "release-1.8" "v1.8-docs" #genversionwithcli "release-1.9" "v1.9-docs" #genversionwithcli "release-1.10" "v1.10-docs" #genversionwithcli "release-1.11" "v1.11-docs" +#genversionwithcli "release-1.12" "v1.12-docs" -genversionwithcli "release-1.12" "$LATEST_VERSION" +genversionwithcli "release-1.13" "$LATEST_VERSION" # Rather than generate the same docs again for /docs, copy from the latest version From 6ead2483af0382b58217c3134bdb2f24522f1d83 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:07:41 +0200 Subject: [PATCH 144/264] fix spelling Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 17 ++++ .../docs/release-notes/release-notes-1.13.md | 94 +++++++++---------- 2 files changed, 64 insertions(+), 47 deletions(-) diff --git a/.spelling b/.spelling index c37419c58b..a32d71275b 100644 --- a/.spelling +++ b/.spelling @@ -615,6 +615,7 @@ unlabelled v1.27.1 v0.6.0. v4.4.1 +v1.13.0 liveness apiservices arm64 @@ -631,6 +632,22 @@ Rollout rollout JKS-formatted changeit +ctl +PrivateCA +structs +klog +relabelings + +FlorianLiebhart +cypres +ubergesundheit +gdvalle +rouke-broersma +schrodit +zhangzhiqiangcs +arukiidou +Richardds +kahirokunn # TEMPORARY # these are temporarily ignored because the spellchecker diff --git a/content/docs/release-notes/release-notes-1.13.md b/content/docs/release-notes/release-notes-1.13.md index b25657df11..6f52d65a0d 100644 --- a/content/docs/release-notes/release-notes-1.13.md +++ b/content/docs/release-notes/release-notes-1.13.md @@ -7,7 +7,7 @@ description: 'cert-manager release notes: cert-manager 1.13' cert-manager 1.13 brings support for DNS over HTTPS, support for loading options from a versioned config file for the cert-manager controller, and more. This release also includes the promotion of -the StableCertificateRequestName and SecretsFilteredCaching feature gates to Beta. +the `StableCertificateRequestName` and `SecretsFilteredCaching` feature gates to Beta. ### Major Themes @@ -25,41 +25,41 @@ DNS01 verification. The DNS self-check method to be used is controlled through t `--dns01-recursive-nameservers-only=true` in combination with `--dns01-recursive-nameservers=https://` (e.g. `https://1.1.1.1/dns-query`) -This is very useful in case all traffic must be HTTP(S) trafic, eg. when using a HTTPS_PROXY. +This is very useful in case all traffic must be HTTP(S) traffic, e.g. when using a `HTTPS_PROXY`. -#### StableCertificateRequestName and SecretsFilteredCaching feature gates promoted to Beta +#### `StableCertificateRequestName` and `SecretsFilteredCaching` feature gates promoted to Beta -The StableCertificateRequestName and SecretsFilteredCaching feature gates have been promoted to Beta. +The `StableCertificateRequestName` and `SecretsFilteredCaching` feature gates have been promoted to Beta. This means that they are enabled by default and that we will not remove them in the future. In case you are experiencing issues with these features, please let us know. The feature gates can still be disabled -by setting the feature gate to false (eg. in case you are experiencing issues with these features). We +by setting the feature gate to false (e.g. in case you are experiencing issues with these features). We plan to promote these feature gates to GA in the future, which will mean that they can no longer be disabled. ### Community -Welcome to these new cert-manager members (more info - https://github.com/cert-manager/cert-manager/pull/6260): -@jsoref -@FlorianLiebhart -@hawksight -@erikgb - -Thanks again to all open-source contributors with commits in this release, including: -@AcidLeroy -@FlorianLiebhart -@lucacome -@cypres -@erikgb -@ubergesundheit -@jkroepke -@jsoref -@gdvalle -@rouke-broersma -@schrodit -@zhangzhiqiangcs -@arukiidou -@hawksight -@Richardds -@kahirokunn +Welcome to these new cert-manager members (more info - https://github.com/cert-manager/cert-manager/pull/6260): +@jsoref +@FlorianLiebhart +@hawksight +@erikgb + +Thanks again to all open-source contributors with commits in this release, including: +@AcidLeroy +@FlorianLiebhart +@lucacome +@cypres +@erikgb +@ubergesundheit +@jkroepke +@jsoref +@gdvalle +@rouke-broersma +@schrodit +@zhangzhiqiangcs +@arukiidou +@hawksight +@Richardds +@kahirokunn Thanks also to the following cert-manager maintainers for their contributions during this release: @SgtCoDFish @@ -67,11 +67,11 @@ Thanks also to the following cert-manager maintainers for their contributions du @irbekrm @inteon -Equally thanks to everyone who provided feedback, helped users and raised issues on Github and Slack and joined our meetings! +Equally thanks to everyone who provided feedback, helped users and raised issues on GitHub and Slack and joined our meetings! Special thanks to @AcidLeroy for adding "load options from a versioned config file" support for the cert-manager controller! This has been on our wishlist for a very long time. (see https://github.com/cert-manager/cert-manager/pull/5337) -Also, thanks a lot to @FlorianLiebhart for adding support for DNS over HTTPS for the ACME DNS self-check. This is very useful in case all traffic must be HTTP(S) trafic, eg. when using a HTTPS_PROXY. (see https://github.com/cert-manager/cert-manager/pull/5003) +Also, thanks a lot to @FlorianLiebhart for adding support for DNS over HTTPS for the ACME DNS self-check. This is very useful in case all traffic must be HTTP(S) traffic, e.g. when using a `HTTPS_PROXY`. (see https://github.com/cert-manager/cert-manager/pull/5003) Thanks also to the [CNCF](https://www.cncf.io/), which provides resources and support, and to the AWS open source team for being good community members and for their maintenance of the [PrivateCA Issuer](https://github.com/cert-manager/aws-privateca-issuer). @@ -82,14 +82,14 @@ In addition, massive thanks to [Venafi](https://www.venafi.com/) for contributin #### Feature - Add support for logging options to webhook config file. (https://github.com/cert-manager/cert-manager/pull/6243, https://github.com/inteon) -- Add view permissions to the well-known (Openshift) user-facing `cluster-reader` aggregated cluster role (https://github.com/cert-manager/cert-manager/pull/6241, https://github.com/erikgb) -- Certificate Shim: distinguish dns names and ip address in certificate (https://github.com/cert-manager/cert-manager/pull/6267, https://github.com/zhangzhiqiangcs) +- Add view permissions to the well-known (OpenShift) user-facing `cluster-reader` aggregated cluster role (https://github.com/cert-manager/cert-manager/pull/6241, https://github.com/erikgb) +- Certificate Shim: distinguish DNS names and IP address in certificate (https://github.com/cert-manager/cert-manager/pull/6267, https://github.com/zhangzhiqiangcs) - Cmctl can now be imported by third parties. (https://github.com/cert-manager/cert-manager/pull/6049, https://github.com/SgtCoDFish) - Make `enableServiceLinks` configurable for all Deployments and `startupapicheck` Job in Helm chart. (https://github.com/cert-manager/cert-manager/pull/6292, https://github.com/ubergesundheit) -- Promoted the StableCertificateRequestName and SecretsFilteredCaching feature gates to Beta (enabled by default). (https://github.com/cert-manager/cert-manager/pull/6298, https://github.com/inteon) +- Promoted the `StableCertificateRequestName` and `SecretsFilteredCaching` feature gates to Beta (enabled by default). (https://github.com/cert-manager/cert-manager/pull/6298, https://github.com/inteon) - The cert-manager controller options are now configurable using a configuration file. (https://github.com/cert-manager/cert-manager/pull/5337, https://github.com/AcidLeroy) -- The pki CertificateTemplate functions now perform validation of the CSR blob, making sure we sign a Certificate that matches the IsCA and (Extended)KeyUsages that are defined in the CertificateRequest resource. (https://github.com/cert-manager/cert-manager/pull/6199, https://github.com/inteon) -- [helm] Add prometheus.servicemonitor.endpointAdditionalProperties to define additional properties on a ServiceMonitor endpoint, e.g. relabelings (https://github.com/cert-manager/cert-manager/pull/6110, https://github.com/jkroepke) +- The `pki.CertificateTemplate*` functions now perform validation of the CSR blob, making sure we sign a Certificate that matches the `IsCA` and `(Extended)KeyUsages` that are defined in the CertificateRequest resource. (https://github.com/cert-manager/cert-manager/pull/6199, https://github.com/inteon) +- [helm] Add `prometheus.servicemonitor.endpointAdditionalProperties` to define additional properties on a ServiceMonitor endpoint, e.g. relabelings (https://github.com/cert-manager/cert-manager/pull/6110, https://github.com/jkroepke) #### Design @@ -98,32 +98,32 @@ In addition, massive thanks to [Venafi](https://www.venafi.com/) for contributin #### Bug or Regression -- Allow overriding default pdb .minAvailable with .maxUnavailable without setting .minAvailable to null (https://github.com/cert-manager/cert-manager/pull/6087, https://github.com/rouke-broersma) -- BUGFIX: `cmctl check api --wait 0` exited without output and exit code 1; we now make sure we perform the API check at least once and return with the correct error code (https://github.com/cert-manager/cert-manager/pull/6109, https://github.com/inteon) -- BUGFIX: the issuer and certificate-name annotations on a Secret were incorrectly updated when other fields are changed. (https://github.com/cert-manager/cert-manager/pull/6147, https://github.com/inteon) +- Allow overriding default PDB `.minAvailable` with `.maxUnavailable` without setting `.minAvailable` to null (https://github.com/cert-manager/cert-manager/pull/6087, https://github.com/rouke-broersma) +- BUGFIX[ctl]: `cmctl check api --wait 0` exited without output and exit code 1; we now make sure we perform the API check at least once and return with the correct error code (https://github.com/cert-manager/cert-manager/pull/6109, https://github.com/inteon) +- BUGFIX[controller]: the issuer and certificate-name annotations on a Secret were incorrectly updated when other fields are changed. (https://github.com/cert-manager/cert-manager/pull/6147, https://github.com/inteon) - BUGFIX[cainjector]: 1-character bug was causing invalid log messages and a memory leak (https://github.com/cert-manager/cert-manager/pull/6232, https://github.com/inteon) - Fix CloudDNS issuers stuck in propagation check, when multiple instances are issuing for the same FQDN (https://github.com/cert-manager/cert-manager/pull/6088, https://github.com/cypres) -- Fix indentation of Webhook NetworkPolicy matchLabels in helm chart. (https://github.com/cert-manager/cert-manager/pull/6220, https://github.com/ubergesundheit) +- Fix indentation of Webhook NetworkPolicy `matchLabels` in helm chart. (https://github.com/cert-manager/cert-manager/pull/6220, https://github.com/ubergesundheit) - Fixed Cloudflare DNS01 challenge provider race condition when validating multiple domains (https://github.com/cert-manager/cert-manager/pull/6191, https://github.com/Richardds) - Fixes a bug where webhook was pulling in controller's feature gates. ⚠️ ⚠️ BREAKING ⚠️ ⚠️ : If you deploy cert-manager using helm and have `.featureGates` value set, the features defined there will no longer be passed to cert-manager webhook, only to cert-manager controller. Use `webhook.featureGates` field instead to define features to be enabled on webhook. **Potentially breaking**: If you were, for some reason, passing cert-manager controller's features to webhook's `--feature-gates` flag, this will now break (unless the webhook actually has a feature by that name). (https://github.com/cert-manager/cert-manager/pull/6093, https://github.com/irbekrm) - Fixes an issue where cert-manager would incorrectly reject two IP addresses as being unequal when they should have compared equal. This would be most noticeable when using an IPv6 address which doesn't match how Go's `net.IP.String()` function would have printed that address. (https://github.com/cert-manager/cert-manager/pull/6293, https://github.com/SgtCoDFish) -- We disabled the `enableServiceLinks` option for our ACME http solver pods, because the option caused the pod to be in a crash loop in a cluster with lot of services. (https://github.com/cert-manager/cert-manager/pull/6143, https://github.com/schrodit) -- ⚠️ possibly breaking: Webhook validation of CertificateRequest resources is stricter now: all KeyUsages and ExtendedKeyUsages must be defined directly in the CertificateRequest resource, the encoded CSR can never contain more usages that defined there. (https://github.com/cert-manager/cert-manager/pull/6182, https://github.com/inteon) +- We disabled the `enableServiceLinks` option for our ACME HTTP solver pods, because the option caused the pod to be in a crash loop in a cluster with lot of services. (https://github.com/cert-manager/cert-manager/pull/6143, https://github.com/schrodit) +- ⚠️ possibly breaking: Webhook validation of CertificateRequest resources is stricter now: all `KeyUsages` and `ExtendedKeyUsages` must be defined directly in the CertificateRequest resource, the encoded CSR can never contain more usages that defined there. (https://github.com/cert-manager/cert-manager/pull/6182, https://github.com/inteon) #### Other (Cleanup or Flake) -- A subset of the klogs flags have been deprecated and will be removed in the future. (https://github.com/cert-manager/cert-manager/pull/5879, https://github.com/maelvls) +- A subset of the klog flags have been deprecated and will be removed in the future. (https://github.com/cert-manager/cert-manager/pull/5879, https://github.com/maelvls) - All service links in helm chart deployments have been disabled. (https://github.com/cert-manager/cert-manager/pull/6144, https://github.com/schrodit) - Cert-manager will now re-issue a certificate if the public key in the latest CertificateRequest resource linked to a Certificate resource does not match the public key of the key encoded in the Secret linked to that Certificate resource (https://github.com/cert-manager/cert-manager/pull/6168, https://github.com/inteon) -- Chore: When hostNetwork is enabled, dnsPolicy is now set to ClusterFirstWithHostNet. (https://github.com/cert-manager/cert-manager/pull/6156, https://github.com/kahirokunn) -- Cleanup the controller configfile structure by introducing sub-structs. (https://github.com/cert-manager/cert-manager/pull/6242, https://github.com/inteon) +- Chore: When `hostNetwork` is enabled, `dnsPolicy` is now set to `ClusterFirstWithHostNet`. (https://github.com/cert-manager/cert-manager/pull/6156, https://github.com/kahirokunn) +- Cleanup the controller config file structure by introducing nested structs. (https://github.com/cert-manager/cert-manager/pull/6242, https://github.com/inteon) - Don't run API Priority and Fairness controller in webhook's extension apiserver (https://github.com/cert-manager/cert-manager/pull/6085, https://github.com/irbekrm) - Helm: Add apache 2.0 license annotation (https://github.com/cert-manager/cert-manager/pull/6225, https://github.com/arukiidou) -- Make apis/acme/v1/ACMEIssuer.PreferredChain optional in JSON serialization. (https://github.com/cert-manager/cert-manager/pull/6034, https://github.com/gdvalle) -- The SecretPostIssuancePolicyChain now also makes sure that the `cert-manager.io/common-name`, `cert-manager.io/alt-names`, ... annotations on Secrets are kept at their correct value. (https://github.com/cert-manager/cert-manager/pull/6176, https://github.com/inteon) -- The cmctl logging has been improved and support for json logging has been added. (https://github.com/cert-manager/cert-manager/pull/6247, https://github.com/inteon) +- Make `apis/acme/v1/ACMEIssuer.PreferredChain` optional in JSON serialization. (https://github.com/cert-manager/cert-manager/pull/6034, https://github.com/gdvalle) +- The `SecretPostIssuancePolicyChain` now also makes sure that the `cert-manager.io/common-name`, `cert-manager.io/alt-names`, ... annotations on Secrets are kept at their correct value. (https://github.com/cert-manager/cert-manager/pull/6176, https://github.com/inteon) +- The cmctl logging has been improved and support for JSON logging has been added. (https://github.com/cert-manager/cert-manager/pull/6247, https://github.com/inteon) - Updates Kubernetes libraries to `v0.27.2`. (https://github.com/cert-manager/cert-manager/pull/6077, https://github.com/lucacome) - Updates Kubernetes libraries to `v0.27.4`. (https://github.com/cert-manager/cert-manager/pull/6227, https://github.com/lucacome) - We now only check that the issuer name, kind and group annotations on a Secret match in case those annotations are set. (https://github.com/cert-manager/cert-manager/pull/6152, https://github.com/inteon) From 25d74cfac020b59b46fbee76f50a38d7e9ad9baa Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:16:40 +0200 Subject: [PATCH 145/264] add upgrade notes for 1.13 Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .../upgrading/upgrading-1.12-1.13.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 content/docs/installation/upgrading/upgrading-1.12-1.13.md diff --git a/content/docs/installation/upgrading/upgrading-1.12-1.13.md b/content/docs/installation/upgrading/upgrading-1.12-1.13.md new file mode 100644 index 0000000000..d6cff22b27 --- /dev/null +++ b/content/docs/installation/upgrading/upgrading-1.12-1.13.md @@ -0,0 +1,19 @@ +--- +title: Upgrading from v1.12 to v1.13 +description: 'cert-manager installation: Upgrading v1.12 to v1.13' +--- + +When upgrading cert-manager from 1.12 to 1.13, in few cases you might need to take additional steps to ensure a smooth upgrade: + +1. BREAKING: If you deploy cert-manager using helm and have .featureGates value set, the features defined +there will no longer be passed to cert-manager webhook, only to cert-manager controller. Use webhook.featureGates field +instead to define features to be enabled on webhook. (https://github.com/cert-manager/cert-manager/pull/6093, https://github.com/irbekrm) + +2. Potentially breaking: If you were, for some reason, passing cert-manager controller's features to webhook's --feature-gates flag, +this will now break (unless the webhook actually has a feature by that name). (https://github.com/cert-manager/cert-manager/pull/6093, https://github.com/irbekrm) + +3. Potentially breaking: Webhook validation of CertificateRequest resources is stricter now: all KeyUsages and ExtendedKeyUsages must be defined directly in the CertificateRequest resource, the encoded CSR can never contain more usages that defined there. (https://github.com/cert-manager/cert-manager/pull/6182, https://github.com/inteon) + +## Next Steps + +From here on you can follow the [regular upgrade process](./README.md). From 670e8b7816d93aaf080a8e5c3683205a9ab3778a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Wed, 13 Sep 2023 14:00:41 +0200 Subject: [PATCH 146/264] release-process: -E should come before -e MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise, -E doens't apply to the sed commands Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 87839cb85f..05d74eda5a 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -218,10 +218,9 @@ page if a step is missing or if it is outdated. `@maelvls`) with actual links using the following command: ```bash - sed \ + sed -E \ -e 's$#([0-9]+)$[#\1](https://github.com/cert-manager/cert-manager/pull/\1)$g' \ -e 's$@(\w+)$[@\1](https://github.com/\1)$g' \ - -E \ github-release-description.md >release-notes.md ``` From 3b0828a21b49a88617fe2ccbb9d1c7ec7fdfee39 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 13 Sep 2023 14:08:44 +0200 Subject: [PATCH 147/264] fix typos Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/installation/upgrading/upgrading-1.12-1.13.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/installation/upgrading/upgrading-1.12-1.13.md b/content/docs/installation/upgrading/upgrading-1.12-1.13.md index d6cff22b27..f928868455 100644 --- a/content/docs/installation/upgrading/upgrading-1.12-1.13.md +++ b/content/docs/installation/upgrading/upgrading-1.12-1.13.md @@ -5,14 +5,14 @@ description: 'cert-manager installation: Upgrading v1.12 to v1.13' When upgrading cert-manager from 1.12 to 1.13, in few cases you might need to take additional steps to ensure a smooth upgrade: -1. BREAKING: If you deploy cert-manager using helm and have .featureGates value set, the features defined -there will no longer be passed to cert-manager webhook, only to cert-manager controller. Use webhook.featureGates field +1. BREAKING: If you deploy cert-manager using helm and have `.featureGates` value set, the features defined +there will no longer be passed to cert-manager webhook, only to cert-manager controller. Use `webhook.featureGates` field instead to define features to be enabled on webhook. (https://github.com/cert-manager/cert-manager/pull/6093, https://github.com/irbekrm) 2. Potentially breaking: If you were, for some reason, passing cert-manager controller's features to webhook's --feature-gates flag, this will now break (unless the webhook actually has a feature by that name). (https://github.com/cert-manager/cert-manager/pull/6093, https://github.com/irbekrm) -3. Potentially breaking: Webhook validation of CertificateRequest resources is stricter now: all KeyUsages and ExtendedKeyUsages must be defined directly in the CertificateRequest resource, the encoded CSR can never contain more usages that defined there. (https://github.com/cert-manager/cert-manager/pull/6182, https://github.com/inteon) +3. Potentially breaking: Webhook validation of CertificateRequest resources is stricter now: all `KeyUsages` and `ExtendedKeyUsages` must be defined directly in the CertificateRequest resource, the encoded CSR can never contain more usages that defined there. (https://github.com/cert-manager/cert-manager/pull/6182, https://github.com/inteon) ## Next Steps From 85ec920f8c7edd51138924974c09cb6e92dc718c Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 13 Sep 2023 14:18:01 +0200 Subject: [PATCH 148/264] fix search-replace mistake Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/v1.13-docs/manifest.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/v1.13-docs/manifest.json b/content/v1.13-docs/manifest.json index f0efd6c6ca..904792e461 100644 --- a/content/v1.13-docs/manifest.json +++ b/content/v1.13-docs/manifest.json @@ -386,18 +386,18 @@ "routes": [ { "title": "Introduction", - "path": "/v1.13-docs/contributing/google-season-of-v1.13-docs/README.md" + "path": "/v1.13-docs/contributing/google-season-of-docs/README.md" }, { "title": "2022", "routes": [ { "title": "Introduction", - "path": "/v1.13-docs/contributing/google-season-of-v1.13-docs/2022/README.md" + "path": "/v1.13-docs/contributing/google-season-of-docs/2022/README.md" }, { "title": "Improve the Navigation and Structure of the cert-manager Website", - "path": "/v1.13-docs/contributing/google-season-of-v1.13-docs/2022/improve-navigation-and-structure.md" + "path": "/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md" } ] } @@ -504,7 +504,7 @@ }, { "title": "API Reference", - "path": "/v1.13-docs/reference/api-v1.13-docs.md" + "path": "/v1.13-docs/reference/api-docs.md" } ] } From a3e2ab87b39f3f4845921e99b5e2e43eae45a153 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 13 Sep 2023 14:36:37 +0200 Subject: [PATCH 149/264] remove sections in 'frozen' docs that are not necessary (based on feedback) Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/v1.12-docs/contributing/README.md | 68 -- content/v1.12-docs/contributing/building.md | 267 ----- .../contributing/coding-conventions.md | 59 -- .../contributing/contributing-flow.md | 138 --- content/v1.12-docs/contributing/crds.md | 65 -- .../v1.12-docs/contributing/dns-providers.md | 23 - content/v1.12-docs/contributing/e2e.md | 148 --- .../contributing/external-issuers.md | 77 -- .../v1.12-docs/contributing/featuregates.md | 47 - .../google-season-of-docs/2022/README.md | 10 - .../2022/improve-navigation-and-structure.md | 215 ---- .../google-season-of-docs/README.md | 8 - content/v1.12-docs/contributing/importing.md | 42 - content/v1.12-docs/contributing/kind.md | 31 - content/v1.12-docs/contributing/policy.md | 159 --- .../contributing/release-process.md | 599 ----------- content/v1.12-docs/contributing/security.md | 13 - content/v1.12-docs/contributing/sign-off.md | 77 -- .../v1.12-docs/contributing/signing-keys.md | 72 -- .../contributing/third-party-code-donation.md | 79 -- .../installation/supported-releases.md | 289 ------ content/v1.12-docs/manifest.json | 142 --- content/v1.12-docs/projects/README.md | 31 - .../projects/approver-policy/README.md | 422 -------- .../projects/approver-policy/api-reference.md | 978 ------------------ .../v1.12-docs/projects/csi-driver-spiffe.md | 257 ----- content/v1.12-docs/projects/csi-driver.md | 231 ----- content/v1.12-docs/projects/istio-csr.md | 92 -- .../projects/trust-manager/README.md | 373 ------- .../projects/trust-manager/api-reference.md | 478 --------- content/v1.13-docs/contributing/README.md | 68 -- content/v1.13-docs/contributing/building.md | 267 ----- .../contributing/coding-conventions.md | 59 -- .../contributing/contributing-flow.md | 178 ---- content/v1.13-docs/contributing/crds.md | 65 -- .../v1.13-docs/contributing/dns-providers.md | 23 - content/v1.13-docs/contributing/e2e.md | 148 --- .../contributing/external-issuers.md | 77 -- .../v1.13-docs/contributing/featuregates.md | 47 - .../google-season-of-docs/2022/README.md | 10 - .../2022/improve-navigation-and-structure.md | 269 ----- .../google-season-of-docs/README.md | 8 - content/v1.13-docs/contributing/importing.md | 42 - content/v1.13-docs/contributing/kind.md | 31 - content/v1.13-docs/contributing/policy.md | 159 --- .../contributing/release-process.md | 735 ------------- content/v1.13-docs/contributing/security.md | 13 - content/v1.13-docs/contributing/sign-off.md | 77 -- .../v1.13-docs/contributing/signing-keys.md | 72 -- .../contributing/third-party-code-donation.md | 79 -- .../installation/supported-releases.md | 296 ------ content/v1.13-docs/manifest.json | 142 --- content/v1.13-docs/projects/README.md | 31 - .../projects/approver-policy/README.md | 431 -------- .../projects/approver-policy/api-reference.md | 978 ------------------ .../v1.13-docs/projects/csi-driver-spiffe.md | 257 ----- content/v1.13-docs/projects/csi-driver.md | 231 ----- content/v1.13-docs/projects/istio-csr.md | 92 -- .../projects/trust-manager/README.md | 417 -------- .../projects/trust-manager/api-reference.md | 592 ----------- 60 files changed, 11384 deletions(-) delete mode 100644 content/v1.12-docs/contributing/README.md delete mode 100644 content/v1.12-docs/contributing/building.md delete mode 100644 content/v1.12-docs/contributing/coding-conventions.md delete mode 100644 content/v1.12-docs/contributing/contributing-flow.md delete mode 100644 content/v1.12-docs/contributing/crds.md delete mode 100644 content/v1.12-docs/contributing/dns-providers.md delete mode 100644 content/v1.12-docs/contributing/e2e.md delete mode 100644 content/v1.12-docs/contributing/external-issuers.md delete mode 100644 content/v1.12-docs/contributing/featuregates.md delete mode 100644 content/v1.12-docs/contributing/google-season-of-docs/2022/README.md delete mode 100644 content/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md delete mode 100644 content/v1.12-docs/contributing/google-season-of-docs/README.md delete mode 100644 content/v1.12-docs/contributing/importing.md delete mode 100644 content/v1.12-docs/contributing/kind.md delete mode 100644 content/v1.12-docs/contributing/policy.md delete mode 100644 content/v1.12-docs/contributing/release-process.md delete mode 100644 content/v1.12-docs/contributing/security.md delete mode 100644 content/v1.12-docs/contributing/sign-off.md delete mode 100644 content/v1.12-docs/contributing/signing-keys.md delete mode 100644 content/v1.12-docs/contributing/third-party-code-donation.md delete mode 100644 content/v1.12-docs/installation/supported-releases.md delete mode 100644 content/v1.12-docs/projects/README.md delete mode 100644 content/v1.12-docs/projects/approver-policy/README.md delete mode 100644 content/v1.12-docs/projects/approver-policy/api-reference.md delete mode 100644 content/v1.12-docs/projects/csi-driver-spiffe.md delete mode 100644 content/v1.12-docs/projects/csi-driver.md delete mode 100644 content/v1.12-docs/projects/istio-csr.md delete mode 100644 content/v1.12-docs/projects/trust-manager/README.md delete mode 100644 content/v1.12-docs/projects/trust-manager/api-reference.md delete mode 100644 content/v1.13-docs/contributing/README.md delete mode 100644 content/v1.13-docs/contributing/building.md delete mode 100644 content/v1.13-docs/contributing/coding-conventions.md delete mode 100644 content/v1.13-docs/contributing/contributing-flow.md delete mode 100644 content/v1.13-docs/contributing/crds.md delete mode 100644 content/v1.13-docs/contributing/dns-providers.md delete mode 100644 content/v1.13-docs/contributing/e2e.md delete mode 100644 content/v1.13-docs/contributing/external-issuers.md delete mode 100644 content/v1.13-docs/contributing/featuregates.md delete mode 100644 content/v1.13-docs/contributing/google-season-of-docs/2022/README.md delete mode 100644 content/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md delete mode 100644 content/v1.13-docs/contributing/google-season-of-docs/README.md delete mode 100644 content/v1.13-docs/contributing/importing.md delete mode 100644 content/v1.13-docs/contributing/kind.md delete mode 100644 content/v1.13-docs/contributing/policy.md delete mode 100644 content/v1.13-docs/contributing/release-process.md delete mode 100644 content/v1.13-docs/contributing/security.md delete mode 100644 content/v1.13-docs/contributing/sign-off.md delete mode 100644 content/v1.13-docs/contributing/signing-keys.md delete mode 100644 content/v1.13-docs/contributing/third-party-code-donation.md delete mode 100644 content/v1.13-docs/installation/supported-releases.md delete mode 100644 content/v1.13-docs/projects/README.md delete mode 100644 content/v1.13-docs/projects/approver-policy/README.md delete mode 100644 content/v1.13-docs/projects/approver-policy/api-reference.md delete mode 100644 content/v1.13-docs/projects/csi-driver-spiffe.md delete mode 100644 content/v1.13-docs/projects/csi-driver.md delete mode 100644 content/v1.13-docs/projects/istio-csr.md delete mode 100644 content/v1.13-docs/projects/trust-manager/README.md delete mode 100644 content/v1.13-docs/projects/trust-manager/api-reference.md diff --git a/content/v1.12-docs/contributing/README.md b/content/v1.12-docs/contributing/README.md deleted file mode 100644 index aec6b21254..0000000000 --- a/content/v1.12-docs/contributing/README.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: Contributing -description: 'cert-manager contributing guide: Get involved!' ---- - -## Great to See You! - -Whether you're a previous contributor or a first timer looking to get involved, we love -it when the community comes together to improve the project! - -In this "contributing" section we document processes we follow as a project, and include -some details on how to build, test and run cert-manager for development purposes. - -## Meetings - -All cert-manager meetings are open for everyone to join; if you have a question or a suggestion or just want to chat, -please feel free to come along and get involved! - -To get invites you can subscribe to [our mailing list](https://groups.google.com/forum/#!forum/cert-manager-dev) and -you should receive calendar invites by mail shortly after joining. The complexities of calendars mean that some invites -might be sent multiple times depending on your email and calendar providers and you might get some invites for past -or future meetings which have been rescheduled or edited. Sorry about that! - -We have 2 regular repeating meetings: our quick daily check-in and an hour-long community meeting every two weeks. - -If you're having any issues joining our meetings, ensure that you're part of the [`cert-manager-dev`](https://groups.google.com/forum/#!forum/cert-manager-dev) Google group, and always feel free to ask in [Slack](./#slack) for help! - -
              -🔰 All of our meetings happen on London (UK) time; you can add London to the world clocks on your phone to avoid confusion! - -When daylight savings time changes in London might be different to when it changes for you if you live in a place that either -doesn't have DST or which changes on a different schedule like North America or Australia! -
              - -### Daily Check-In - -Our daily check-in meetings [happen on Google Meet](https://meet.google.com/eum-fyvt-xpa) at [10:30 London time](http://www.thetimezoneconverter.com/?t=10:30&tz=Europe/London) every weekday. - -The format is a 5 minute social chat, followed by a quick round-robin status report and ending with any longer form talking points. - -The status report is a stand-up where we talk about work done yesterday, work coming up and highlight any blockers. -We'll try to keep to a **strict time limit** during these status reports of around 1 minute per person. - -Please don't be offended if someone steps in when you run out of time and moves the reports along to the next person - the idea -is for everyone to be succinct so it's clear what's being worked on and who is blocked. - -We finish with talking points, which are open-ended discussions on any topic related to cert-manager or its sub-projects. -We'll ensure that anyone outside of the core maintainer team who has a talking point goes first. - -### Community Meetings - -Our bi-weekly (i.e. every two weeks) community meetings happen [on Google Meet](https://meet.google.com/iga-jwvx-nye) at [17:00 London time](http://www.thetimezoneconverter.com/?t=17:00&tz=Europe/London) (for dates, check calendar invites or [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U)). - -These meetings are an hour-long chat about cert-manager topics. It's a great way to get involved with contributing for the -first time; to get answers to any questions you might have; or to propose a new feature which needs some explanation. - -If you want to discuss something, please add it to the [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U) -before the meeting. The meeting chair will try to get to everything that was on the notes before the meeting started. - -We try to record these meetings and put them on YouTube so they can be checked later - if you don't want to appear on video please keep -your camera off! - -## Slack - -We have two cert-manager channels on [Kubernetes Slack](https://slack.k8s.io) which we use to chat: - -* [`cert-manager`](https://kubernetes.slack.com/messages/cert-manager): for all users of cert-manager; use this one for any usage related questions -* [`cert-manager-dev`](https://kubernetes.slack.com/messages/cert-manager-dev): for collaboration between cert-manager contributors and maintainers; please only use this for code related questions diff --git a/content/v1.12-docs/contributing/building.md b/content/v1.12-docs/contributing/building.md deleted file mode 100644 index 92fd7a996f..0000000000 --- a/content/v1.12-docs/contributing/building.md +++ /dev/null @@ -1,267 +0,0 @@ ---- -title: Building cert-manager -description: 'cert-manager contributing guide: Building cert-manager' ---- - -cert-manager is built and tested using [make](https://www.gnu.org/software/make/manual/make.html), with a focus on using the standard Go tooling -where possible and keeping system dependencies to a minimum. The cert-manager build system can provision most of its dependencies - including Go - -automatically if required. - -cert-manager's build system fully supports developers who use `Linux amd64`, `macOS amd64` and `macOS arm64`. - -It also has limited support for `Linux arm64`, although that platform is largely untested and isn't fully supported. - -Other operating systems and architectures may work but will likely require hacks and workarounds to develop on. - -## Prerequisites - -There are very few other requirements needed for developing cert-manager, and crucially the build system should tell you with a friendly error -message if there's anything missing. If you think an error message which relates to a missing dependency is unhelpful, we consider that a bug and -we'd appreciate if you raised [an issue](https://github.com/cert-manager/cert-manager/issues/new?assignees=&labels=&template=bug.md) to tell us about it! - -You should install the following tools before you start developing cert-manager: - -- [git](https://git-scm.com/) -- [curl](https://curl.se/) -- [GNU make](https://www.gnu.org/software/make/manual/make.html), `v3.82` or newer -- GNU Coreutils (usually already installed on Linux, available via [homebrew](https://formulae.brew.sh/formula/coreutils) for macOS) -- `jq` (available in Linux package managers and in [homebrew](https://formulae.brew.sh/formula/jq)) -- `docker` (or `podman`, see [Container Engines](#container-engines) below) -- `Go` (optional; see [Go Versions](#go-versions) below) - -## Getting Started - -The vast majority of commands which you're likely to need to use are documented via `make help`. That's probably the first place to start if you're -developing cert-manager. We'll also provide an overview on this page of some of the key targets and things to bear in mind. - -### Go Versions - -cert-manager defaults to using whatever version of Go you've installed locally on your system. If you want to use your system Go, that's totally fine. - -Alternatively, make can provision and "vendor" Go specifically for cert-manager, helping to ensure you use the same version that's used in CI and to -make it easier to get started developing. - -To start using a vendored Go, run: `make vendor-go`. - -You only need to run `vendor-go` once and it'll be "sticky", being used for all future make invocations in your local checkout. - -To return to using your system version of go, run: `make unvendor-go`. - -To check which version of Go is _currently_ being used, run: `make which-go`, which prints the version number of Go and the path to the Go binary. - -```console -# Use a vendored version of go -$ make vendor-go -cd _bin/tools/ && ln -f -s ../downloaded/tools/_go-1.XY.Z-linux-amd64/goroot . -cd _bin/tools/ && ln -f -s ../downloaded/tools/_go-1.XY.Z-linux-amd64/goroot/bin/go . - -# A path to go inside the cert-manager directory indicates that a vendored Go is being used -$ make which-go -go version go1.XY.Z linux/amd64 -go binary used for above version information: /home/user/workspace/cert-manager/_bin/tools/go - -# Go back to the system Go -$ make unvendor-go -rm -rf _bin/tools/go _bin/tools/goroot - -# The binary is now "go" which should be found in $PATH -$ make which-go -go version go1.AB.C linux/amd64 -go binary used for above version information: go -``` - -### Go Workspaces - -In short: Some development tools will complain about cert-manager's module layout; to help with this, generate a -`go.work` file using `make go-workspace`. - -The cert-manager repository as of cert-manager 1.12 contains multiple Go modules, in a setup where only the core module `github.com/cert-manager/cert-manager` -is expected to be imported by third party modules. There are separate modules (which we call submodules), all of which have replace statements for the core -cert-manager module. - -This setup is intentional to convey that these submodules are not intended to be imported by third parties, and to ensure that each submodule always uses -whatever the cert-manager core module version is at the same commit - but this structure can have the side effect that certain development tools and scripts -will not work as expected. - -As an example, `go test ./...` will by default only affect the core module. To test, say, the controller, you'd need to use `cd cmd/controller && go test ./...`. - -This can be avoided through the use of go workspaces, which will handle local replacements for you and work better with editors such as VS Code. - -You can run `make go-workspace` to generate a `go.work` file which should enable `go test ./...` to work across the -whole repo, and which should help editors to understand the module structure. - -Note that go workspaces are not used when testing pull requests in CI. If you see errors in CI which you can't replicate -locally, try building with the `GOWORK` environment variable set to `off` or deleting the `go.work` file. - -### Parallelism - -The cert-manager Makefile is designed to be highly parallel wherever possible. Any build and test commands should be able to be executed in parallel using -standard Make functionality. - -One important caveat is that that Go will default to detecting the number of cores available on the system and spinning up as many threads as it can. If you're -using Make functionality to run multiple builds in parallel, this number of threads can be excessive and actually lead to slower builds. - -It's possible to limit the number of threads Go uses we'd generally recommend doing so when using Make parallelism. - -The best values to use will depend on your system, but we've had success using around half of the available number of cores for Make and limiting Go to between -2 and 4 threads per core. - -For example, using an 8-core machine: - -```bash -# Run 4 make targets in parallel, and limit each `go build` to 2 threads. -make GOMAXPROCS=2 -j4 release-artifacts -``` - -## Testing - -cert-manager's build pipeline and CI infrastructure uses the same Makefile that you use when developing locally, -so there should be no divergence between what the tests run and what you run. That means you should be able to be pretty confident that any changes you make -won't break when tested in CI. - -### Running Local Changes in a Cluster - -It's common that you might want to run a local Kubernetes cluster with your locally-changed copy of cert-manager in it, for manual testing. - -There are make targets to help with this; see [Developing with Kind](./kind.md) for more information. - -### Unit and Integration Tests - -First of all: If you want to test using `go test`, feel free! For unit tests (which we define as any test outside of the `test/` directory), `go test` will -work on a fresh checkout. - -Note that the cert-manager repo is split into multiple modules and unless you're using go workspaces `go test ./...` won't actually run all tests. See [Go Workspaces](./building.md#go-workspaces) above for more details. - -Integration tests may require some external tools to be set up first, so to run the integration tests inside `test/` you might need to run: - -```bash -make setup-integration-tests -``` - -Helper targets are also available which use [`gotestsum`](https://github.com/gotestyourself/gotestsum) for prettier output. It's also possible to -configure these targets to run specific tests: - -```bash -# Run all unit and integration tests -make test - -# Run only unit tests -make unit-test - -# Run only integration tests -make integration-test - -# Run all tests in pkg -make WHAT=./pkg/... test - -# Run unit and integration tests exactly as run in CI -# (NB: usually not needed - this is mostly for JUnit test output for dashboards) -make test-ci -``` - -### End-to-End Testing - -cert-manager's end-to-end tests are a little more involved and have [dedicated documentation](./e2e.md) describing their use. - -### Other Checks - -We run a variety of other tools on every Pull Request to check things like formatting, import ordering and licensing. These checks can all be run locally: - -```bash -make ci-presubmit -``` - -NB: One of these checks currently requires Python 3 to be installed, which is a unique requirement in the code base. We'd like to remove that requirement in the future. - -## Updating CRDs and Code Generation - -Changes to cert-manager's CRDs require some code generation to be done, which will be checked on every pull request. - -If you make changes to cert-manager CRDs, you'll need to run some commands locally before raising your PR. - -This is documented in our [CRDs](./crds.md) section. - -## Building - -cert-manager produces many artifacts for a lot of different OS / architecture combinations, including: - -- Container images -- Client binaries (`cmctl` and `kubectl_cert-manager`) -- Manifests (Helm charts, static YAML) - -All of these artifacts can be built locally using make. - -### Containers - -cert-manager's most important artifacts are the containers which actually run cert-manager in a cluster. We default to using `docker` for this, -but aim to support docker-compatible CLI tools such as `podman`, too. See [Container Engines](#container-engines) for more info. - -There are several targets for building different cert-manager containers locally. These will all default to using `docker`: - -```bash -# Build everything for every architecture -make all-containers - -# Build just the controller containers on every architecture -make cert-manager-controller-linux - -# As above, but for the webhook, cainjector, acmesolver and cmctl containers -make cert-manager-webhook-linux -make cert-manager-cainjector-linux -make cert-manager-acmesolver-linux -make cert-manager-ctl-linux -``` - -#### Container Engines - -NB: This section doesn't apply to end-to-end tests, which might not work outside of Docker at the time of writing. See the [end-to-end documentation](./e2e.md#container-engines) -for more information. - -It's possible to use an alternative container engine to build cert-manager containers. This has been successfully tested using `podman`. - -Configure an alternative container engine by setting the `CTR` variable: - -```bash -# Build everything for every architecture, using podman -make CTR=podman all-containers -``` - -### Client Binaries - -Both `cmctl` and `kubectl_cert-manager` can be built locally for a release. These binaries are built for Linux, macOS and Windows across several architectures. - -```bash -# Build all cmctl binaries for all platforms, then for linux only, then for macOS only, then for Windows only -make cmctl -make cmctl-linux -make cmctl-darwin -make cmctl-windows - -# As above but for kubectl_cert-manager -make kubectl_cert-manager -make kubectl_cert-manager-linux -make kubectl_cert-manager-darwin -make kubectl_cert-manager-windows -``` - -### Manifests - -We use "manifests" as a catch-all term for non-binary artifacts which we build as part of a release including static installation YAML and our Helm chart. - -Everything can be built using make: - -```bash -make helm-chart -make static-manifests -``` - -### Everything - -Sometimes it's useful to build absolutely everything locally, to be sure that a change didn't break some obscure architecture and to build confidence when raising a PR. - -It's not easy to build a _complete_ release locally since a full release includes signatures which depend on KMS keys being configured. Most users probably don't -need that, though, and for this use case there's a make target which will build everything except the signed artifacts: - -```bash -make GOMAXPROCS=2 -j4 release-artifacts -``` diff --git a/content/v1.12-docs/contributing/coding-conventions.md b/content/v1.12-docs/contributing/coding-conventions.md deleted file mode 100644 index 8001cc387d..0000000000 --- a/content/v1.12-docs/contributing/coding-conventions.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Coding Conventions -description: 'cert-manager contributing guide: Coding conventions' ---- - -cert-manager, like most Go projects, delegates almost all stylistic choices to `gofmt`, -with `goimports` on top for organizing imports. Broadly speaking, if you set your editor to run -`goimports` when you save a file, your code will be stylistically correct. - -cert-manager generally also follows the Kubernetes -[coding conventions](https://www.kubernetes.dev/docs/guide/coding-convention/) and the Google -[Go code review comments](https://github.com/golang/go/wiki/CodeReviewComments). - -## Organizing Imports - -Imports should be organized into 3 blocks, with each block separated by two newlines: - -```go -import ( - "stdlib" - - "external" - - "internal" -) -``` - -An example might be the following, taken from -[`pkg/acme/accounts/client.go`](https://github.com/cert-manager/cert-manager/blob/0c71fe7795858b96cabcddabf706d997cd2fba3f/pkg/acme/accounts/client.go): - -```go -import ( - "crypto/rsa" - "crypto/tls" - "net" - "net/http" - "time" - - acmeapi "golang.org/x/crypto/acme" - - acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" - acmeutil "github.com/cert-manager/cert-manager/pkg/acme/util" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - "github.com/cert-manager/cert-manager/pkg/metrics" - "github.com/cert-manager/cert-manager/pkg/util" -) -``` - -Once this manual split of standard library, external and internal imports has been made, it will be -enforced automatically by `goimports` when executed in the future. - -## UK vs. US spelling - -For the sake of consistency, cert-manager uses en-US spelling for the -documentation in https://cert-manager.io as well as within the cert-manager -codebase. A comprehensive list of en-GB → en-US word substitution is available -on Ubuntu's -[`WordSubstitution`](https://wiki.ubuntu.com/EnglishTranslation/WordSubstitution) -page. \ No newline at end of file diff --git a/content/v1.12-docs/contributing/contributing-flow.md b/content/v1.12-docs/contributing/contributing-flow.md deleted file mode 100644 index 4706a0d169..0000000000 --- a/content/v1.12-docs/contributing/contributing-flow.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: Contributing Flow -description: 'cert-manager contributing guide: Contribution flow' ---- - -All of cert-manager's development is done via -[GitHub](https://github.com/cert-manager/cert-manager) which contains code, issues and pull -requests. - -All code for the documentation and cert-manager.io can be found at [the cert-manager/website repo](https://github.com/cert-manager/website/). -Any issues towards the documentation should also be filed there. - -## GitHub bot - -We use [Prow](https://github.com/k8s-ci-robot/test-infra/tree/master/prow) on all our repositories. -If you've ever looked at a Kubernetes repo, you will probably already have met Prow. Prow will be able to help you in GitHub using its commands. -You can find then all [on the command help page](https://prow.build-infra.jetstack.net/command-help). -Prow will also run all tests and assign certain labels on PRs. - -## Bugs - -All bugs should be tracked as issues inside the -[GitHub](https://github.com/cert-manager/cert-manager/issues) repository. Issues should then be -attached with the `kind/bug` tag. To do this add `/kind bug` to your issue description. -This may then be assigned a priority and milestone to be addressed in a future release. - -The more logs and information you can give about what and how the bug has been -discovered, the faster it can be resolved. - -Critical bug fixes are typically also cherry picked to the current minor stable releases. - -> Note: If you are simply looking for _troubleshooting_ then you should post -> your question to the community `cert-manager` [slack channel](https://slack.k8s.io). -> Many more people read this channel than GitHub issues, it's likely your problem will -> be solved quicker by using Slack. -> Please also check that the bug has not already been filed by searching for key -> terms in the issue search bar. - -### (Re)opening and closing issues - -Prow can assist you to reopen or close issues you file, you can trigger it using `/reopen` or `/close` in a GitHub Issue comment. - -## Features - -Feature requests should be created as -[GitHub](https://github.com/cert-manager/cert-manager/issues) issues. They should contain -clear motivation for the feature you wish to see as well as some possible -solutions for how it can be implemented. -Issues should then be tagged with `kind/feature`. To do this add `/kind feature` to your issue description. - -> Note: It is often a good idea to bring your feature request up on the -> community `cert-manager` [slack channel](https://slack.k8s.io) to discuss whether -> the feature request has already been made or is aligned with the project's -> priorities. - -## Creating Pull Requests - -Changes to the cert-manager code base is done via [pull -requests](https://github.com/cert-manager/cert-manager/pulls). Each pull request -should ideally have a corresponding issue attached that is to be fixed by this -pull request. It is valid for multiple pull requests to resolve a single issue -in the interest of keeping code changes self contained and simpler to review. - -Once created, a team member will assign themselves for review and enable -testing. To make sure the changes get merged, keep an eye out for reviews which -can have multiple cycles. - -If the pull request is a critical bug fix then this will probably -also be cherry picked to the current stable version of cert-manager as a patch -release. - -To let people know that your PR is still a work in progress, we usually add a -`WIP:` prefix to the title of the PR. Prow will then automatically set the label -`do-not-merge/work-in-progress`. - - -### Cherry Picking - -If the pull request contains a critical bug fix then this should be cherry picked in to the current stable cert-manager branch -and [released as a patch release](../installation/supported-releases.md#support-policy). - -To trigger the cherry-pick process, add a comment to the GitHub PR. -For example: -``` -/cherry-pick release-x.y -``` - -The `jetstack-bot` will then create a new branch and a PR against the release branch, -which should be reviewed, approved and merged using the process described above. - -### DCO signoff - -All commits in the PR should be signed off, more info on how to do this is at the [DCO Sign Off](./sign-off.md) page. -Exceptions can only be made for small documentation fixes. - -## Project Management - -Most of cert-manager's project management is done on GitHub, with the help of Prow. - -### When will something be released? - -Our team works using [GitHub milestones](https://github.com/cert-manager/cert-manager/milestones). -When a milestone is set on an Issue it is generally an indication of when we plan to address this. -Prow will apply milestones on merged PRs, this will tell you in which version that PR will land. - -The milestone page will also have an indicated due date when we will release. This might have some delay. -We brief our users/contributors about this in our bi-weekly community meeting, for an up to date status report we recommend joining these. - -### Labels - -We make a heavy use of GitHub labels for PRs and Issues. The ones on PRs are mostly managed by Prow and code reviewers. -In issues we always aim to add 3 types: area, priority and kind. These are set using Prow using `/area`, `/kind` and `/priority`. -Sometimes `/triage` is also added which helps us when following up Issues. - -* Area indicates the code area which is/will need changing -* Kind indicates if it is a `bug` or a `feature` but also can be `documentation` or `cleanup` (general maintenance) -* Priority is the priority it has for the cert-manager team, PRs are still very welcome for those! - -### Assignees meaning in PRs and issues - -Sometimes, you might see someone commenting with the -[`/assign` prow command](https://prow.build-infra.jetstack.net/command-help#assign): - -```plain -/assign @meyskens -``` - -Here is the meaning that we give to the GitHub assignees: - -- On issues, it means that the assignee is working on it. -- On PRs, we use it as a way to know who should be taking a look at the PR at any time: - - When the author is assigned, it means the PR needs work to be done aka "changes requested"; - - When nobody is assigned, it means this PR needs review; - - When someone different from the author is assigned, it means this person is reviewing this PR. - -### Triage Party! - -Every few weeks we will plan a Triage Party meeting, where we use the (Triage Party)[https://triage.build-infra.jetstack.net/] tool to go recent/old issues to prioritise them so we can address them in a timely matter. These meetings are open to everyone and invites will be sent out using our mailing list (warning: despite the word party these meetings are sometimes boring). \ No newline at end of file diff --git a/content/v1.12-docs/contributing/crds.md b/content/v1.12-docs/contributing/crds.md deleted file mode 100644 index 1cce203032..0000000000 --- a/content/v1.12-docs/contributing/crds.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: CRDs -description: 'cert-manager contribution guide: CRDs' ---- - -cert-manager uses [Kubernetes Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) to define -the resources which users interact with when using cert-manager, such as `Certificate`s and `Issuer`s. - -When changes are made to the CRDs in code, there are a couple of extra steps which are required. - -## Generating CRD Updates - -We use [`controller-gen`](https://book.kubebuilder.io/reference/controller-gen.html) to update our CRDs, and [`k8s-code-generator`](https://github.com/kubernetes/code-generator) -for code generation. - -Verifying and updating CRDs and generated code can be done entirely through make. There are two steps; one will update CRDs and one will update generated code: - -```bash -# Check that CRDs and codegen are up to date -make verify-crds verify-codegen - -# Update CRDs based on code -make update-crds - -# Update generated code based on CRD defintions in code -make update-codegen -``` - -## Versions - -cert-manager currently has a single `v1` API version for public use. - -cert-manager API types are defined in [`pkg/apis/certmanager`](https://github.com/cert-manager/cert-manager/tree/master/pkg/apis/certmanager). - -ACME related resources are in [`pkg/apis/acme`](https://github.com/cert-manager/cert-manager/tree/master/pkg/apis/acme). - -### Code Comments - -Code comments on API type fields are converted into documentation on this website as well as appearing in the output of `kubectl explain`. - -That means that `go doc`-style comments on API fields should be written to be user-facing and not developer-facing. For this reason it's also fine to break from -usual Go standards regarding code comments when editing these fields. - -### Internal API Versions - -cert-manager also has an internal API version which lives under [`internal/apis`](https://github.com/cert-manager/cert-manager/tree/master/internal/apis). - -The internal version is only used for validation and conversion and controllers should not generally use it; it's not intended to be user-friendly or stable and can change. -However all new fields also have to be added here for the conversion logic to work. - -For details on conversion and versions, see the [official Kubernetes docs for CRD versioning](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/). - -## Kubebuilder - -While cert-manager doesn't fully use Kubebuilder, CRDs can make use of special Kubebuilder flags such as [validation flags](https://book.kubebuilder.io/reference/markers/crd-validation.html). - -## Making Changes to APIs - -Please see our [API compatibility promise](../installation/api-compatibility.md) for details on which types of changes to APIs are acceptable. - -Generally, the gist is that new fields can be added but that existing fields cannot be removed. - -This also means that when a field is added to a version of the API, it's permanent and its name cannot be changed. Because of this, we try to be cautious when adding new fields. - -The same principles apply to [constants and enumerated types](https://kubernetes.io/docs/reference/using-api/deprecation-policy/#enumerated-or-constant-values). diff --git a/content/v1.12-docs/contributing/dns-providers.md b/content/v1.12-docs/contributing/dns-providers.md deleted file mode 100644 index 400f3d17ee..0000000000 --- a/content/v1.12-docs/contributing/dns-providers.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: DNS Providers -description: 'cert-manager contributing guide: Creating DNS providers' ---- - -## Creating DNS Providers - -Due to the large number of requests to support DNS providers to resolve DNS -challenges, it became impractical and infeasible to maintain and test all DNS -providers in the main cert-manager repository. - -For this reason, it was decided that new DNS providers should be supported out-of-tree -by way of external webhooks. - -To implement an external DNS provider webhook, it is recommended to base your -implementation on the [cert-manager webhook-example](https://github.com/cert-manager/webhook-example). - -There's further information available in the configuration section: - -- [ACME DNS01 via webhook](../configuration/acme/dns01/README.md#webhook) -- [Configuring an ACME issuer with external webhook](../configuration/acme/dns01/webhook.md) - -If you're struggling with creating a new DNS webhook, reach out on [Slack](./README.md#slack)! diff --git a/content/v1.12-docs/contributing/e2e.md b/content/v1.12-docs/contributing/e2e.md deleted file mode 100644 index 9e6b9e7409..0000000000 --- a/content/v1.12-docs/contributing/e2e.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -title: Running End-to-End Tests -description: 'cert-manager contribuing guide: End-to-end (E2E) tests' ---- - -cert-manager has an extensive end-to-end (e2e) test suite that verifies functionality against a -real Kubernetes cluster. - -The full end-to-end test suite can take a long time to complete and is run against every pull -request made to the cert-manager project. - -Unless you've made huge changes to the cert-manager codebase --- or to the end-to-end -tests themselves --- you probably don't _need_ to run the tests locally. If you do want to -run the tests, though, this document explains how. - -
              -The status of each commit on the master branch is reported on -[`testgrid.k8s.io`](https://testgrid.k8s.io/jetstack-cert-manager-master). Join the -[`cert-manager-dev-alerts`](https://groups.google.com/g/cert-manager-dev-alerts) -Google group to receive email notifications when tests fail. -
              - -## Requirements - -There are no special requirements for the end-to-end tests. All dependencies can be -provisioned automatically through the make build system. - -## Set up End-to-End Tests - -### Create a Cluster - -You can create a kind cluster using Make: - -```console -# Create a cluster using whatever K8s version is default, named "kind" -make e2e-setup-kind - -# Create a cluster using K8s 1.23 named "keith" -make K8S_VERSION=1.23 KIND_CLUSTER_NAME=keith e2e-setup-kind -``` - -**IMPORTANT:** the kind cluster will be set up using a specific service CIDR range to enable certain functionality in end-to-end tests. This CIDR range is not currently configurable. - -Once complete, the cluster is available via `kubectl` as you'd expect. - -### Install Test Dependencies - -There are various dependencies which the end-to-end tests require, all of which can also -be installed via Make: - -```console -make e2e-setup -``` - -If you only need to update or reinstall one of these dependencies in your test cluster, you can instead install named components explicitly to save some time. - -The most common use case for this is to **reinstall cert-manager itself**, say if you've made a change -locally and want to test that change in a cluster: - -```console -# Most important: reinstall cert-manager, including rebuilding changed containers locally -make e2e-setup-certmanager - -# An example of reinstalling something else; reinstall bind -make e2e-setup-bind - -# More generally, see make/e2e-setup.mk for different targets! -``` - -## Run End-to-End Tests - -As with setup, running tests is available through make. In fact, you can just run `make e2e` directly -and avoid having to set anything up manually! - -```console -# Set up a cluster using the defaults if one's not already present, and then run the end-to-end tests -make e2e - -# Set up a K8s 1.23 cluster and then run tests -make K8S_VERSION=1.23 e2e - -# Run tests exactly as they're run in CI; usually not needed -make e2e-ci -``` - -If you don't want to run every test you can focus on specific tests using `GINKGO_FOCUS` syntax, as described in the -[Ginkgo documentation](https://onsi.github.io/ginkgo/#focused-specs): - -```console -make GINKGO_FOCUS=".*my test description" e2e -``` - -## Cluster IP Details - -As mentioned above, the end-to-end tests expect that certain components are deployed in a -specific way and even at specific IP addresses. - -By way of illustration, the following cluster components are deployed with specific IPs: - -| Component / Make Target | Used in | IP | DNS A Record | -| -------------------------- | -------------------------- | ----------- | --------------------------------------- | -| `e2e-setup-bind` | DNS-01 tests | `10.0.0.16` | | -| `e2e-setup-ingressnginx` | HTTP-01 `Ingress` tests | `10.0.0.15` | `*.ingress-nginx.db.http01.example.com` | -| `e2e-setup-projectcontour` | HTTP-01 `GatewayAPI` tests | `10.0.0.14` | `*.gateway.db.http01.example.com` | - -If you don't set these components up correctly, you might see that the ACME HTTP01 (and other) end-to-end tests fail. - -## End-to-End Test Structure - -The end-to-end tests consist of 2 main parts: issuer specific tests and the conformance suite. - -Both parts use [Ginkgo](https://onsi.github.io/ginkgo/#getting-ginkgo) to run their tests under the hood. - -### Conformance Suite - -#### RBAC - -This suite tests all RBAC permissions granted to cert-manager on the cluster to check that it is able to operate correctly. - -#### Certificates - -This suite tests certificate functionality against all issuers. - -#### Feature Sets - -Some issuers don't support certain features, such as for example issuing Ed25519 certificates or adding an email address -to the X.509 SAN extension. - -Each test specifies a used feature using `s.checkFeatures(feature)`, which is then checked against the issuer's -`UnsupportedFeatures` list. Tests which use a feature unsupported by an issuer are skipped for that issuer. - -### Cloud Provider Tests - -The master branch of cert-manager can also be tested against different cloud providers. Currently, tests for [EKS](https://aws.amazon.com/eks/) are present which run as a periodic job once every two days. - -#### Extending The Cloud Provider Tests - -The infrastructure used to run the e2e tests on cloud providers is present in the [cert-manager/test-infra](https://github.com/cert-manager/test-infra) repository. More cloud providers can be added by creating infrastructure for them using [Terraform](https://www.terraform.io/). - -Apart from that, tests for the existing infrastructure can be customized by editing their respective prow jobs present in the [Jetstack testing repository](https://github.com/cert-manager/testing/tree/master/config/jobs/cert-manager) repository. Values like the cert-manager version or the cloud provider version are present as variables in Terraform so their values can be changed when using `terraform apply` in the prow jobs, for example, for the [EKS prow job](https://github.com/cert-manager/testing/blob/master/config/jobs/cert-manager/cert-manager-periodics.yaml#L524) the cert-manager version being tested can be changed using - -```console -terraform apply -var="cert_manager_version=v1.3.3" -auto-approve -``` - -To see a list of all configurable variables present for a particular infrastructure you can see the `variables.tf` file for that cloud provider's [infrastructure](https://github.com/cert-manager/test-infra). - -> Please note that the cloud provider tests run the e2e tests present in the **master** branch of cert-manager on a predefined version of cert-manager (can be changed in the prow job). Currently, they do **not** test code in a PR, but we have an [issue](https://github.com/cert-manager/cert-manager/issues/4349) tracking that request. diff --git a/content/v1.12-docs/contributing/external-issuers.md b/content/v1.12-docs/contributing/external-issuers.md deleted file mode 100644 index 4d18572fb3..0000000000 --- a/content/v1.12-docs/contributing/external-issuers.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: Implementing External Issuers -description: 'cert-manager contributing guide: External Issuers' ---- - -cert-manager offers a number of [core issuer types](../configuration/README.md) that represent -various certificate authorities. - -Since the number of potential issuers is larger than what could reasonably be supported in the -main cert-manager repository, cert-manager also supports out-of-tree external issuers, and treats -them the same as in-tree issuer types. - -This document is for people looking to _create_ external issuers. For more information on how to -install and configure external issuer types, read the [configuration documentation](../configuration/external.md). - -## General Overview - -An issuer represents a certificate authority that signs incoming certificate -requests. In cert-manager, the `CertificateRequest` resource represents a single -request for a signed certificate, containing the raw certificate request PEM -data as well as other information relating to the desired certificate. - -In cert-manager, each issuer type has its own controller that watches these -`CertificateRequest` resources and checks to see if a given `CertificateRequest` is -configured to use the issuer. - -This is done via the `issuerRef` stanza on the `CertificateRequest` which contains -an issuer `name`, `kind` and `group`. - -`group` denotes an API group such as `cert-manager.io` (which is responsible for all core issuer types). - -`kind` denotes the "kind" resource type of the issuer - usually `Issuer` or `ClusterIssuer`. - -`name` denotes the name of the issuer resource of the specified kind. An example might be `my-ca-issuer`. - -When an issuer controller observes a new `CertificateRequest` which refers to it, -it then ensures that the corresponding issuer resource exists in Kubernetes. - -It then uses the information inside the issuer resource to attempt to create a -signed certificate, based upon the information inside the certificate request. - -## Sample External Issuer - -If you want to create an External Issuer, the best place to start is likely to be the [Sample External Issuer](https://github.com/cert-manager/sample-external-issuer). - -The Sample External Issuer is maintained by the cert-manager team, and its README file has step-by-step instructions -on how to write an external issuer using Kubebuilder and controller-runtime. - -## Approval - -Before signing a certificate, Issuers **must** also ensure that the `CertificateRequest` is -[`Approved`](../concepts/certificaterequest.md#approval). - -If the `CertificateRequest` is not `Approved`, the issuer **must** not process it. Issuers are not -responsible for approving `CertificateRequests` and should refuse to proceed if they find a certificate -that is not approved. - -If a `CertificateRequest` created for an issuance associated with a `Certificate` gets [`Denied`](../concepts/certificaterequest.md#approval), the issuance will be failed by cert-manager's issuing controller. - -## Conditions - -Once a signed certificate has been gathered by the issuer controller, it updates the status of the -`CertificateRequest` resource with the signed certificate. It is then important to update the condition -status of that resource to a ready state, as this is what is used to signal to higher order -controllers - such as the `Certificate` controller - that the resource is ready to be consumed. - -Conversely, if the `CertificateRequest` fails, it is as important to mark the resource as such, as this will -also be used as a signal to higher order controllers. Valid condition states are listed under [concepts](../concepts/certificaterequest.md#conditions). - -## Implementation - -It is recommended that you make use of the [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder) project in order -to implement your external issuer controller. This makes it very simple to generate `CustomResourceDefinitions` and gives -you a lot of controller functionality out of the box. - -If you have further questions on how to implement an external issuer controller, it is best to reach out on [slack](./README.md#slack) -or to join a [community calls](./README.md#meetings). diff --git a/content/v1.12-docs/contributing/featuregates.md b/content/v1.12-docs/contributing/featuregates.md deleted file mode 100644 index 4e6f332967..0000000000 --- a/content/v1.12-docs/contributing/featuregates.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -Title: Implementing feature gates -description: 'cert-manager contributing guide: Implementing feature gates' ---- - -As of v1 release cert-manager is considered stable. We aim to follow Kubernetes API compatibility policy when making API changes, see [API compatibility](../installation/api-compatibility.md) to avoid breaking users' existing cert-manager installations. -This means that as developers we are somewhat limited in regards to changing existing behavior, i.e renaming or removing API elements or changing their behavior. - -New functionality that is not yet stable[^1] can still be added, but it needs to be placed behind a feature gate. - -## Feature gated API fields - -Feature gated API fields are implemented using `--feature-gates` flags of cert-manager [webhook](../cli/webhook.md) and [controller](../cli/controller.md). - -A feature gated API field is always visible to the user (i.e when running `kubectl explain `), but is only functional if the relevant feature is explicitly enabled via feature flags for both the webhook and controller. - -If a user attempts to apply a resource with the feature gated field set to a non-nil value, but the feature gate is not enabled, the resource will get rejected by the webhook validation. -This mechanism differs from [the one that Kubernetes uses for feature gated API field implementation](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md#new-field-in-existing-api-version) where the field will be simply set to nil if the feature gate is disabled. We chose to use webhook validation instead to make debugging easier for users who are attempting to use the feature gated field, but have forgotten to enable the feature gate. - - -### Implementation - -- Implement the new field and document that it is feature gated and in order to use it the controller and webhook feature gates need enabling -- Add a new [webhook feature gate](https://github.com/cert-manager/cert-manager/blob/3a055cc2f56c1c2874807af4a8f84d0a1c46ccb4/internal/webhook/feature/features.go#L25-L39) for the field -- Update webhook validation checks for the relevant resource kind to ensure that if the feature gated field is set, but the webhook feature gate is not enabled, the resource gets rejected -- Add a new [controller feature gate](https://github.com/cert-manager/cert-manager/blob/2417132b3cd017b5f0974006e03c2b8a540efe3f/internal/controller/feature/features.go#L26-L54) for the field -- Ensure that any control loops that use the feature, check that the feature gate is actually enabled. (This is required to cover edge cases such as if the webhook runs a version of cert-manager where the feature is in GA whereas controller runs an older version where the feature is still in experimental state) -- Ensure that the feature gate is added to cert-manager installation scripts for CI and local tests in [make](https://github.com/cert-manager/cert-manager/blob/134398e939bb2b1401697eaf589405ad469cd609/make/e2e-setup.mk#L165) and [bazel](https://github.com/cert-manager/cert-manager/blob/fd747b42b9ab4b6409b61b7946e8dc14d532e950/devel/addon/certmanager/install.sh#L26) scripts -- Default cert-manger e2e CI tests run with all feature gates for all components enabled. There is an additional optional e2e test that runs with all feature gates disabled. You can trigger that for your PR with `/test pull-cert-manager-e2e-feature-gates-disabled` to verify that all works as expected both with and without the new feature gate. - -### Potential issues - -- The person deploying cert-manager has to remember to set two cert-manager feature gates, one of the webhook one on the controller for the feature to function. Forgetting to set one of them might result in unexpected behavior - -- A user must remember to remove the alpha fields from their manifests when disabling a previously enabled API feature. Failing to do so might result in unexpected behavior- for example forgetting to remove feature gated field from a `Certificate` resource might result in failed renewals at some later point when cert-manager's controller will attempt to update the `Certificate` spec, but the webhook will reject the update due to the feature gated field being set. - -### References - -- cert-manager's [API compatibility promise](../installation/api-compatibility.md) - -- An example implementation of an alpha field is [`AdditionalOutputFormats` field on `Certificate` spec](https://github.com/cert-manager/cert-manager/blob/dbad3d98f3d7d85cadb4bd2c2493faf8b666b313/internal/apis/certmanager/types_certificate.go#L169-L174) - -- [Kubernetes definition of feature stages](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages) - -- Kubernetes [API change design](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md) - -[^1]: For example, functionality that might change in the future in response to user feedback diff --git a/content/v1.12-docs/contributing/google-season-of-docs/2022/README.md b/content/v1.12-docs/contributing/google-season-of-docs/2022/README.md deleted file mode 100644 index 587036e7e1..0000000000 --- a/content/v1.12-docs/contributing/google-season-of-docs/2022/README.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Google Season of Docs 2022 -description: Google season of docs 2022 proposal ---- - -We registered our interest to participate in Google Season of Docs 2022! - -There's one project proposal: - -[Improve the Navigation and Structure of the cert-manager Website](./improve-navigation-and-structure.md) diff --git a/content/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md b/content/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md deleted file mode 100644 index 8f23636af3..0000000000 --- a/content/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md +++ /dev/null @@ -1,215 +0,0 @@ ---- -title: Improve the Navigation and Structure of the cert-manager Website -description: Google season of docs 2022 proposal ---- - -## Project Updates - -### 7 Sept 2022: The Webhook Debugging Guide - -friction log for task 3, before -friction log for task 3, after - -At the start of the Google Season of Docs program, we built friction logs for -common user tasks, such as debugging the error "connect: connection refused". -The friction log for this task, visible in the [GSoD work -document](https://docs.google.com/document/d/1O-MFWwtpOcNlrRzsiBvrpGHC10EXnvw0XK37M2nEjzg/edit#bookmark=id.cu9ss8s7yl46), -was to serve as a reference point to see whether the improvements we aimed to -bring would have an impact or not. - -The friction log showed a consistent pattern: the user searches the error on -Google, is confused by GitHub issues that don't have any solutions, then clicks -the second link in the Google results, without much luck. We realized that one -improvement we could make was to add a link to the FAQ page "Troubleshooting -Problems with the Webhook". We found two problems with this FAQ page: - -1. It could not be found by anyone because the error messages were not listed in - the page, meaning that Google would not show the page in the search results. -2. Many error messages were not listed in the page. - -We set ourselves to rewrite this page with the goal of making it error-focused, -meaning that the user would just be able to look for their particular error and -start debugging it. We called it "The Definitive Debugging Guide for the -cert-manager Webhook Pod", and it can be found -[here](../../../troubleshooting/webhook.md). - -### 12 Aug 2022: Improved the layout of the navigation menu - -On displays `>=1280px` the left-hand menu was too narrow to display the nested menu items clearly, -On smaller displays the [responsive CSS](https://tailwindcss.com/docs/responsive-design) actually made the menu larger. -So we've widened it by 1 column on displays `>=1280px` and reduced the width of the content by 1 column to compensate. -This makes the menu much easier to read on laptop and desktop computer screens. - -We fixed an inconsistency in the vertical spacing between menu items with sub-menus and those without. - -And finally, we moved the version selector to the bottom of the side-bar to avoid distracting the reader. - -### 3 August 2022: The cert-manager.io Documentation Survey is now closed - -Thank you to everyone who participated in our documentation survey. -We will use the results to prioritize sections of the website for restructuring and rewriting. -Before the conclusion of this Season-of-Docs we will select a random winner from among the responses and contact you about your prize. - -### 18 July 2022: The cert-manager.io Documentation Survey - -Screenshot 2022-07-18 at 14-35-48 cert-manager documentation survey - -We have created a short survey, to help us identify what are the top-priorities for the cert-manager.io documentation. - -1. We want identify the most useful documentation, so that we don't go and change things that are already working well. -2. We want to know which documentation is not useful, so that we can make improvements. -3. We'd like to hear from new and experienced users about how and how often you use the documentation. -4. And we'd like to know where else you find good information about cert-manager, outside of the cert-manager.io website, -so that we can try and incorporate some of those sources. - -We've added a link to the survey to the banner at the top of this site -and we will also be sharing the link in our Slack channels and mailing lists. - -[Please take 10 minutes to fill in the survey](https://docs.google.com/forms/d/e/1FAIpQLSeqfRkd86_N0L7VOW_ImCT0iyUabhczdiDk2dQDLp55V8kqvw/viewform). - -
              - -### 15 July 2022: New "Getting Started" pages - - - - -We have been auditing the existing documentation to identify some key tasks that our users and potential new users need to carry out. -We have created "friction logs" for some of these tasks. -What this means is that we imagine ourselves in the place of the user and ask, for example, - -> How can I get a Let's Encrypt certificate for my server in Kubernetes? - -So we searched Google and DuckDuckGo for "Let's Encrypt Kubernetes" and to our surprise, cert-manager.io does not feature among the top search results. - -Among the results are some excellent third-party tutorials and videos about using cert-manager to create Let's Encrypt certificates, -and we are grateful to the authors for taking the time to write such detailed content. -But inevitably, some of these refer to much older versions of cert-manager and Kubernetes. -So we have decided to write some official guides, for the cert-manager.io website which demonstrate how to quickly install cert-manager and configure it for Let's Encrypt. -We hope that in time these will be indexed by the search engines and that they will reach the top of the search results for "Let's Encrypt Kubernetes". -The advantages will be that users and potential users will find up-to-date information, -and the cert-manager.io maintainers will receive fewer support requests from new users who are attempting this task. - -Go and read the new [Getting Started Guide for GKE Users](../../../getting-started) and tell us what you think. - -
              - -### 5 May 2022: Announcing Mehak Saeed as Technical Writer - -We are delighted to announce that [Mehak Saeed](https://www.linkedin.com/in/mehak-saeed-29121a12a) will be the technical writer working on this project. -We were extremely impressed with Mehak's presentation during her interview and impressed with her detailed preparations and planning. -We look forward to working with her. - -Thank you to all the other technical writers who applied for this project. - -### 14 April 2022: Project Accepted - -This project was [accepted on 14 April 2022](https://developers.google.com/season-of-docs/docs/participants). - -### 24 March 2022: Project Registered - -We have [registered our interest to participate in Google Season of Docs 2022](https://github.com/google/season-of-docs/pull/483), -and have submitted a single project proposal detailed in the remaining of this -page. - -You have until 27 April 2022 18:00 UTC to apply for the technical writer role. - -We will be sharing the name of the selected candidate on Wed 4 May 2022 at -15:00 London Time (14:00 UTC) on Slack in the channel `#cert-manager-dev`. - -To apply as a technical writer, please let us know by one of the two ways -below: - -- e-mail us at `cert-manager-maintainers@googlegroups.com` with the prefix - `GSoD2022:` in the e-mail subject. -- or open an issue on - [cert-manager/website](https://github.com/cert-manager/website) with the - prefix `GSoD2022:` in the issue title. - -You can join our open standup (every day at 10:30 UK time), and join the -Kubernetes Slack channel `#cert-manager-dev` to know more about this project -proposal. - -## About cert-manager - -cert-manager (current version 1.8.0, first release in October 2017) is an Apache-2.0 licensed Kubernetes add-on to automate the management and issuance of TLS certificates. - -Our typical contributors are Go developers from around the world with experience of the Kubernetes ecosystem with experience contributing to core Kubernetes components and Kubernetes operators. - -Our users are often developers and system administrators who are trying to automate the rotation of TLS certificates for applications running in their Kubernetes clusters. - -Our largest users have cert-manager installed on multiple Kubernetes clusters and managing many thousands of TLS certificates. - -## Project Overview - -Right now the content is not designed with our target audiences in mind. -For example a new user will not easily find a guide explaining how to install cert-manager on AWS and configure it for Let’s Encrypt. -Nor will a Cluster Administrator easily find information about how to optimize cert-manager for a large cluster with many Certificates. -The information exists but is spread across multiple pages and is often not at the obvious page. - -As a visual example, a user looking for a guide on how the Certificate resource can be used may feel helpless when realizing that the "Certificate" page exists twice: once under the "Usage" section, and once under the "Concepts" section. - -![Screenshot of the cert-manager.io website with Usage and Concepts visible in the menu](/images/google-season-of-docs-2022-improve-navigation-and-structure.png) - -(NB: This screenshot is from our old site design but the text and layout are broadly the same) - -We would like a technical writer: - -1. to help us identify our target audiences, and -2. to identify the key tasks of each of these audiences, and -3. re-structure the cert-manager.io website with this in mind. - -For example, we have discussed the following audiences and tasks: Beginner, Cluster Administrator, User, Integrator, New Contributor -and each of these people will be interested in a different set of tasks. -We would like them to quickly and easily find the information they need. - -By making it easier for each group to find the information they need we aim to reduce the number of support queries. - -## Scope - -The scope of this project is as follows: - -1. Identify and describe three target audiences. -2. Identify three key top tasks for each of these audiences. -3. Audit the existing documentation and create a friction log of the current documentation. -4. Using the friction log as a baseline, re-organize the documentation to minimize friction for three top tasks. -6. Incorporate feedback from documentation testers (volunteers in the project) and the wider cert-manager community. -7. Work with the cert-manager team to publish the documentation on cert-manager.io. -8. Create documentation for website contributors explaining how we structure our content around audiences and tasks. - -## Measuring success - -After the technical writer has helped us identify the 3 key tasks for each audience -we will measure a baseline number of clicks required to achieve the task and we will aim to minimize the number of clicks for each task. - -## Timeline - -| Dates | Action Items | -|-------------|--------------------------------------------------| -| May | Orientation | -| May / June | Identify audiences and tasks | -| May / June | Audit and friction log | -| June | Restructuring tasks | -| June / July | Incorporating feedback | -| June / July | Publish to cert-manager.io | -| July | Finish writing guidance for website contributors | -| July | Project Completion | - -## Budget - -| Budget item | Amount ($) | Running Total ($) | Notes | -|-------------------------------------------------------------------------------|-------------|-------------------|---------------------------------| -| Technical writer audit and restructuring of the cert-manager.io documentation | 12,000 | 12,000 | | -| Volunteer stipends | 1,500 | 13,500 | 3 volunteer stipends x 500 each | -| TOTAL | | 13,500 | | - -Regarding the amount of $12,000, we assume that it will be enough to fund one experienced technical writer -part-time (for example, they could work half day from Tuesday to Friday, for a total of 24 days, for 3 months -at a daily rate of $500). - -We will give the "volunteer stipend" to contributors who can show they have one PR within the project -time frame (from 1st May to 30th July) in which a re-write of one page or a set of pages. Before -starting the rewriting, the volunteer will suggest which page they wish to work on either on Slack -(Kubernetes Slack, channel #cert-manager-dev), or in an issue on GitHub, and make sure by asking the -team whether it makes sense to rework this page. As long as at least one positive reaction, the -volunteer can start working. For the stipend to be validated, the PR needs to be reviewed and merged. diff --git a/content/v1.12-docs/contributing/google-season-of-docs/README.md b/content/v1.12-docs/contributing/google-season-of-docs/README.md deleted file mode 100644 index cb43f69aa8..0000000000 --- a/content/v1.12-docs/contributing/google-season-of-docs/README.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Google Season of Docs -description: cert-manager and Google Season of Docs ---- - -The cert-manager project participated in Google Season of Docs 2022 - -If you're interested in what happened, you can check out our [2022 proposals!](./2022/README.md). diff --git a/content/v1.12-docs/contributing/importing.md b/content/v1.12-docs/contributing/importing.md deleted file mode 100644 index 2c39a247e3..0000000000 --- a/content/v1.12-docs/contributing/importing.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Importing cert-manager in Go -description: 'cert-manager contributing guide: Importing cert-manager' ---- - -cert-manager is written in Go, and uses Go modules. You _can_ import it as a Go module, and in some cases -that's fine or even encouraged, but as a rule we generally recommend against importing cert-manager. - -Generally speaking, except for the cases listed below under [When You Might Import cert-manager](#when-you-might-import-cert-manager), -code in the cert-manager repository is *not* covered under any Go module compatibility guarantee. We can and will make breaking -changes, even in publicly exported Go code and even in a minor or patch release of cert-manager. We have made breaking changes like -this in the past. - -Note that this doesn't affect _running_ cert-manager. Our commitment on compatibility is to not break the runtime -functionality of cert-manager, and we take that seriously. - -If you're certain that you *do* need to import cert-manager as a module, see [Module Import Paths](#module-import-paths) -below for a note on how to do that. - -## When You Might Import cert-manager - -You might need to import cert-manager if you're writing Go code which: - -- uses cert-manager custom resources, so you want to import something under `pkg/apis` -- implements an external DNS solver webhook, as in the [webhook-example](https://github.com/cert-manager/webhook-example) -- implements an external issuer, as in the [sample-external-issuer](https://github.com/cert-manager/sample-external-issuer) - -If you think you really need to import other parts of the code, please do reach out and [talk to us](./README.md#slack) so we're -aware of this need! We'll always try to avoid breakage where we can. - -## Module Import Paths - -For all supported versions of cert-manager, the module import path is `github.com/cert-manager/cert-manager`. - -Historically, the cert-manager repository was created on GitHub as `https://github.com/jetstack/cert-manager`, and was later -migrated to `https://github.com/cert-manager/cert-manager`. - -This means that the Go module import path you need may be different if you're trying to use an older version of cert-manager. - -For cert-manager 1.8 and later, use the new path listed above. - -For cert-manager versions older than 1.8 use the old path: `github.com/jetstack/cert-manager` diff --git a/content/v1.12-docs/contributing/kind.md b/content/v1.12-docs/contributing/kind.md deleted file mode 100644 index a6fc15c14a..0000000000 --- a/content/v1.12-docs/contributing/kind.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Developing with Kind -description: 'cert-manager contributing guide: Using Kind' ---- - -[Kind](https://kind.sigs.k8s.io/) allows you to provision Kubernetes clusters locally using nested Docker containers, -with no requirement for virtual machines. - -These clusters are quick to create and destroy, and are useful for simple testing for -development. cert-manager also uses kind clusters in its [end-to-end tests](./e2e.md). - -## Using Kind Locally - -You should be able to make use of cert-manager's end-to-end test setup logic to create a local Kind cluster for -development. As such, if you want a local cluster you might want to follow some of the details in the -[end-to-end test documentation](./e2e.md). - -If, though, you just want to get a cluster up and running with your local changes to cert-manager running inside -`kind`, try the following: - -```console -make e2e-setup-kind e2e-setup-certmanager -``` - -Or, if you need a specific version of Kubernetes: - -```console -make K8S_VERSION=1.xx e2e-setup-kind e2e-setup-certmanager -``` - -That should leave you with a working cluster which you can interact with using `kubectl`! diff --git a/content/v1.12-docs/contributing/policy.md b/content/v1.12-docs/contributing/policy.md deleted file mode 100644 index d29f34e41f..0000000000 --- a/content/v1.12-docs/contributing/policy.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: Feature Policy -description: 'cert-manager contributing guide: Contribution Policy' ---- - -We love to receive both feature requests and PRs which add to and improve cert-manager; the community is at the heart of what we do! - -If you're thinking of adding a feature, we recommend you read this doc to maximize the chances of your contribution getting the attention it deserves and hopefully to get it merged quickly! - -We recommend creating an issue first for it to be discussed with the cert-manager maintainers. Another possibility is bringing it up in a community meeting for an open discussion on the implementation. - -## Feature Sizing: Getting Your Change Accepted - -We evaluate new features and PRs based on their size and their significance; either they're small or large. - -### Smaller Features - -Many contributions are small. That usually - but not always - means that implementing them won't require many lines of code to be added or changed, and in any case they should be easy -for maintainers to review. A PR being small is a good thing; if you can down-scope your feature to make it smaller, we won't complain! - -If you believe your feature is small, please feel free to just raise a PR and optionally also post a link to your PR in the [cert-manager-dev slack channel](./README.md#slack). Usually a sufficiently small PR can be merged without too much ceremony. If we think it's actually a larger piece of work, we'll let you know. - -### Larger Features - -If you're not sure whether your PR is small, or if you know it's bigger, you'll want to speak to us first before raising a PR. This -will help to ensure that your PR is something we're likely to merge to avoid wasting your time. It'll also make it easier -for us to do the design process. - -#### Design Documents - -Larger feature development should normally start with a design discussion. To get that started, you would raise a PR with a design document against [cert-manager/cert-manager/design](https://github.com/cert-manager/cert-manager/tree/master/design). This allows us to discuss the proposed functionality before starting the work to implement it and serves as a way to document the decisions and reasoning behind them. Ideally, a good design document should allow for faster and more consistent feature development and implementation process by providing a single place where all potential concerns and questions are answered. - -We have a [design template](https://github.com/cert-manager/cert-manager/blob/master/design/template.md) that outlines the structure of the document. -(This is a simplified version of [Kubernetes enhancements KEP template](https://github.com/kubernetes/enhancements/tree/master/keps/NNNN-kep-template)). -Do reach out if you need help with the design. - -Part of the process of discussing a design document may also include a video call with you included! That helps us to plan how a feature should -be implemented and approached. It'll be pretty informal and casual; we just want to make sure we're all on the same page. This call might be part -of a biweekly meeting. - -#### Making Progress with Larger Features - -Larger features with a design document are much more likely to be accepted, and in turn we're much more likely to commit a single -named cert-manager maintainer to the effort to help the PR to be successful. That maintainer might not be able to answer all your -questions, but they should certainly be able to point you in the right direction. - -To get in touch to discuss a feature, please reach out on the [cert-manager-dev slack channel](./README.md#slack), or join a [cert-manager public meeting](./README.md#meetings) to talk about your proposal. - -If you have an open PR with a design document (or have some questions about how to proceed with a design), you should absolutely feel free to add the PR with your design or a link to the relevant GitHub issue to the [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U/edit) for our next biweekly meeting -and join in so we're sure to discuss it and so you can contribute to the discussion! - -#### Large Feature Lifecycle - -1. Informally ask about the feature in slack or a public meeting -2. Create a PR with a lightweight design document using the [design template](https://github.com/cert-manager/cert-manager/tree/master/design/template.md), for discussion -3. Design doc PR gets reviewed - possibly includes meeting or discussion in a biweekly meeting -4. Implement your feature, helped and reviewed by a named cert-manager maintainer - -## Feature Requests We'll Likely Reject - -In some cases, people will request features which we've previously rejected or which for some reason we have to reject. - -It's nothing personal; sometimes we have to make tough choices and especially when it comes to security and maintainability we have to reject certain -proposals. If your feature request is listed below, there's a high chance we'll have to reject it. - -That said, if you think we've made a mistake and that we should reconsider, we're open to chatting - consider joining our [biweekly meetings](./README.md#meetings) to discuss it with us! - -### Vendoring Kubernetes related APIs outside of the `k8s.io/` namespace - -Vendoring project APIs that also vendor `k8s.io/apimachinery`, such as OpenShift, Contour, or Velero, is not recommend because the Kubernetes dependency is likely to conflict with cert-manager's instance. -It could also cause a conflict with different Kubernetes client versions being used. - -If this is needed it is suggested to use a "dynamic client" that converts the objects into internal structures copied into the cert-manager codebase. - -### Additional configuration options for the Helm chart - -cert-manager's Helm chart is intended to allow to create a standard, best practices cert-manager installation with basic configuration options, such as being able to provide flags to cert-manager components, label resources etc. -We do not aim to include every possible configuration option for resources that the chart creates to avoid maintenance burden and because we do not have automated testing for all chart configuration options. Therefore we are likely to not accept PRs that add advanced or niche configuration options to Helm charts- we recommend that users who require that configuration use another mechanism such as [Helm's post-install hooks](https://helm.sh/docs/topics/charts_hooks/). - -### Helm + CRDs - -Helm suggests that CRDs be included in a `crds/` subdirectory of a chart, with the `crd-install` annotation included. This has the unfortunate side effect that CRDs are not upgraded if changed in a later release. - -CRDs being upgraded without being removed and re-installed is essential for cert-manager to move forward. - -This was previously discussed [in the Helm community](https://github.com/helm/helm/issues/5871). - -cert-manager works around this limitation by shipping CRDs in the templates. - -### Helm Subchart capabilities - -cert-manager now has the capability to be [installed as a subchart](../installation/helm.md#installing-cert-manager-as-subchart). - -But you need to be careful when adding it to your umbrella chart. - -This is because the cert-manager installation creates cluster scoped resources like admission webhooks and custom resource definitions. cert-manager should be seen as part of your cluster and should be treated as such for being installed. An apt comparison -to other Kubernetes components would be a LoadBalancer controller or a PV provisioner. - -It is your responsibility to ensure that cert-manager is only installed once in your cluster. -This can be managed via the `condition` parameter of the dependency in your `Chart.yaml`, which allows users to disable the installation of a subchart. The condition parameter must be added when using cert-manager as a subchart to allow users to disable your dependency. - -```yaml -apiVersion: v2 -name: example_chart -description: A Helm chart with cert-manager as subchart -type: application -version: 0.1.0 -appVersion: "0.1.0" -dependencies: - - name: cert-manager - version: v1.8.0 - repository: https://charts.jetstack.io - alias: cert-manager - condition: cert-manager.enabled -``` - -### Secret injection or copying - -cert-manager deals with very sensitive information (all TLS certificates for your services) and has cluster-level access to secret resources. As such, when designing features we need to consider all of the ways these secrets might be abused to escalate privilege. - -Secret data is meant to be securely stored in `Secret` resources and have narrow scoped access privileges for unauthorized users. Because of this, we won't usually add any functionality that allows this data to be copied/injected into any resource -other than a Kubernetes `Secret`. - -#### cainjector - -The cainjector component is a special exception to this rule as it deals in non-sensitive information (CAs, not cert/key pairs). This component is able to inject the `ca.crt` file into predefined fields on `ValidatingWebhookConfiguration`, `MutatingWebhookConfiguration`, and `CustomResourceDefinition` resources from Certificate resources. - -These 3 components are already scoped only for privileged users, and will already give you cluster scoped access to resources. - -If you’re designing a resource that needs a CA Certificate or TLS key pair it is strongly recommended to use a reference to a secret instead of embedding it in a resource. - -### Cross namespace resources - -Namespace boundaries in Kubernetes provide a barrier for access scopes. Apps or users can be limited to only access resources in a certain namespace. - -cert-manager is a controller that operates on cluster wide resources however, and while it may seem interesting to allow access to copy or write certificate data from one namespace to the other, this can cause a bypass of the -namespace security model for all users, which is usually not intended and can be a major a security issue. - -We don't support this behavior; if you believe you need it, and it's intended for your use case then there are other Kubernetes controllers that can do this, although we'd suggest extreme caution. - -### Sign certificates using the Kubernetes CA - -Kubernetes has a Certificate Signing Requests API, and a `kubectl certificates` command which allows you to approve certificate signing requests and have them signed by the certificate authority (CA) of the Kubernetes cluster. This -CA is generally used for your nodes. - -This API and CLI have occasionally been misused to sign certificates for use by pods outside of the control plane; we believe this is a mistake. - -For the security of the Kubernetes cluster it's important to limit access to the Kubernetes certificate authority; such certificates increase the attack surface for the Kubernetes API server since this CA signs certificates for -authorization against the API server. If cert-manager used this cert, it could allow any user with permission to create cert-manager resources to elevate privileges by signing certificates which are trusted for API access. - -[See our FAQ](../faq/README.md#kubernetes-has-a-builtin-certificatesigningrequest-api-why-not-use-that) for more details on this. - -### Integrations with third party infrastructure providers - -We try to not include in core cert-manager new functionality that involves calling third party APIs that we don't have infrastructure to test (or that the maintainers don't have the skills to work with). - -Instead we try to build interfaces such as [external DNS webhook solver](../configuration/acme/dns01/webhook.md) that can be implemented to use cert-manager with a particular third party implementation. -We believe that this is a more sustainable approach as that way folks who have knowledge and skills to work with particular infrastructure can own a project that interacts with it and it lets us avoid merging potentially untested code to core cert-manager. -An example of a PR that might be rejected would be adding a new external DNS solver kind, see https://github.com/cert-manager/cert-manager/pull/1088 diff --git a/content/v1.12-docs/contributing/release-process.md b/content/v1.12-docs/contributing/release-process.md deleted file mode 100644 index 82d2318899..0000000000 --- a/content/v1.12-docs/contributing/release-process.md +++ /dev/null @@ -1,599 +0,0 @@ ---- -title: Release Process -description: 'cert-manager contributing: Release process' ---- - -This document aims to outline the process that should be followed for -cutting a new release of cert-manager. If you would like to know more about -current releases and the timeline for future releases, take a look at the -[Supported Releases](../installation/supported-releases.md) page. - -## Prerequisites - -⛔️ Do not proceed with the release process if you do not meet all of the -following conditions: - -1. The relevant [testgrid dashboard](https://testgrid.k8s.io/cert-manager) should not be failing for the release you're trying to perform. -2. The release process **takes about 40 minutes**. You must have time to complete all the steps. -3. You currently need to be at Jetstack to get the required GitHub and GCP - permissions. (we'd like contributors outside Jetstack to be able to get - access; if that's of interest to you, please let us know). -4. You need to have the GitHub `admin` permission on the cert-manager project. - To check that you have the `admin` role, run: - - ```bash - brew install gh - gh auth login - gh api /repos/cert-manager/cert-manager/collaborators/$(gh api /user | jq -r .login)/permission | jq .permission - ``` - - If your permission is `admin`, then you are good to go. To request the - `admin` permission on the cert-manager project, [open a - PR](https://github.com/jetstack/platform-board/pulls/new) with a link to - here. - -5. You need to be added as an "Editor" to the GCP project - [cert-manager-release](https://console.cloud.google.com/?project=cert-manager-release). - To check if you do have access, try opening [the Cloud Build - page](https://console.cloud.google.com/cloud-build?project=cert-manager-release). - To get the "Editor" permission on the GCP project, open a PR with your name - added to the maintainers list in - [`cert_manager_release.tf`](https://github.com/jetstack/terraform-jetstack/blob/master/cert_manager_release.tf) - - ```diff - --- a/cert_manager_release.tf - +++ b/cert_manager_release.tf - @@ -17,6 +17,7 @@ locals { - var.personal_email["..."], - var.personal_email["..."], - var.personal_email["..."], - + var.personal_email["mael-valais"], - ]) - } - ``` - - You may use the following PR description: - - ```markdown - Title: Access to the cert-manager-release GCP project - - Hi. As stated in "Prerequisites" on the [release-process][1] page, - I need access to the [cert-manager-release][2] project on GCP in - order to perform the release process. Thanks! - - [1]: https://cert-manager.io/docs/contributing/release-process/#prerequisites - [2]: https://console.cloud.google.com/?project=cert-manager-release - ``` - -This guide applies for versions of cert-manager released using `make`, which should be every version from cert-manager 1.8 and later. - -**If you need to release a version of cert-manager 1.7 or earlier** see [older releases](#older-releases). - -First, ensure that you have all the tools required to perform a cert-manager release: - -1. Install the [`release-notes`](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md) CLI: - - ```bash - go install k8s.io/release/cmd/release-notes@v0.13.0 - ``` - -2. Install our [`cmrel`](https://github.com/cert-manager/release) CLI: - - ```bash - go install github.com/cert-manager/release/cmd/cmrel@latest - ``` - -3. Clone the `cert-manager/release` repo: - - ```bash - # Don't clone it from inside the cert-manager repo folder. - git clone https://github.com/cert-manager/release - cd release - ``` - -4. Install the [`gcloud`](https://cloud.google.com/sdk/) CLI. -5. [Login](https://cloud.google.com/sdk/docs/authorizing#running_gcloud_auth_login) - to `gcloud`: - - ```bash - gcloud auth application-default login - ``` - -6. Make sure `gcloud` points to the cert-manager-release project: - - ```bash - gcloud config set project cert-manager-release - export CLOUDSDK_CORE_PROJECT=cert-manager-release # this is used by cmrel - ``` - -7. Get a GitHub access token [here](https://github.com/settings/tokens) - with no scope ticked. It is used only by the `release-notes` CLI to - avoid API rate limiting since it will go through all the PRs one by one. - -## Minor releases - -A minor release is a backwards-compatible 'feature' release. It can contain new -features and bug fixes. - -### Release schedule - -We aim to cut a new minor release once per month. The rough goals for each -release are outlined as part of a GitHub milestone. We cut a release even if -some of these goals are missed, in order to keep up release velocity. - -### Process for releasing a version - -
              -🔰 Please click on the **Edit this page** button on the top-right corner of this -page if a step is missing or if it is outdated. -
              - -1. Make sure to note which type of release you are doing. That will be helpful - in the next steps. - - | Type of release | Example of git tag | - |------------------------------------|--------------------| - | initial alpha release | `v1.3.0-alpha.0` | - | subsequent alpha release | `v1.3.0-alpha.1` | - | initial beta release | `v1.3.0-beta.0` | - | subsequent beta release | `v1.3.0-beta.1` | - | final release | `v1.3.0` | - | (optional) patch pre-release[^1] | `v1.3.1-beta.0` | - | patch release (or "point release") | `v1.3.1` | - -[^1]: One or more "patch pre-releases" may be created to allow voluntary community testing of a bug fix or security fix before the fix is made generally available. The suffix `-beta` must be used for patch pre-releases. - -2. **(final release only)** Make sure that a PR with the new upgrade - document is ready to be merged on - [cert-manager/website](https://github.com/cert-manager/website). See for - example, see - [upgrading-1.0-1.1](https://cert-manager.io/docs/installation/upgrading/upgrading-1.0-1.1/). - -3. Check that the `origin` remote is correct. To do that, run the following - command and make sure it returns the upstream - `https://github.com/cert-manager/cert-manager.git`: - - ```bash - # Must be run from the cert-manager repo folder. - git remote -v | grep origin - ``` - - It should show: - - ```text - origin https://github.com/jetstack/cert-manager (fetch) - origin https://github.com/jetstack/cert-manager (push) - ``` - -4. Place yourself on the correct branch: - - - **(initial alpha and subsequent alpha)**: place yourself on the `master` - branch: - - ```bash - git checkout master - git pull origin master - ``` - - - **(initial beta only)** The release branch doesn't exist yet, so let's - create it and push it: - - ```bash - # Must be run from the cert-manager repo folder. - git checkout master - git pull origin master - git checkout -b release-1.12 master - git push origin release-1.12 - ``` - - **GitHub permissions**: `git push` will only work if you have the `admin` - GitHub permission on the cert-manager repo to create or push to the - branch, see [prerequisites](#prerequisites). If you do not have this - permission, you will have to open a PR to merge master into the release - branch), and wait for the PR checks to become green. - - - **(subsequent beta, patch release and final release)**: place yourself on - the release branch: - - ```bash - git checkout release-1.12 - git pull origin release-1.12 - ``` - - You don't need to fast-forward the branch because things have been merged - using `/cherry-pick release-1.0`. - - **Note about the code freeze:** - - The first beta starts a new "code freeze" period that lasts until the - final release. Just before the code freeze, we fast-forward everything - from master into the release branch. - - During the code freeze, we continue merging PRs into master as usual. - - We don't fast-forward master into the release branch for the second (and - subsequent) beta, and only `/cherry-pick release-1.0` the fixes that should be part - of the subsequent beta. - - We don't fast-forward for patch releases and final releases; instead, we - prepare these releases using the `/cherry-pick release-1.0` command. - - > Note about branch protection: The release branches are protected by [GitHub branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule), which is [configured automatically by Prow](https://github.com/cert-manager/testing/blob/500b990ad1278982b10d57bf8fbca383040d2fe8/config/config.yaml#L27-L36). - > This prevents anyone *accidentally* pushing changes directly to these branches, even repository administrators. - > If you need, for some reason, to fast forward the release branch, - > you should delete the branch protection for that release branch, using the [GitHub branch protection web UI](https://github.com/cert-manager/cert-manager/settings/branches). - > This is only a temporary change to allow you to update the branch. - > [Prow will re-apply the branch protection within 24 hours](https://docs.prow.k8s.io/docs/components/optional/branchprotector/#updating). - -5. Create the tag for the new release locally and push it upstream (starting the cert-manager build): - - ```bash - RELEASE_VERSION=v1.8.0-beta.0 - git tag -m"$RELEASE_VERSION" $RELEASE_VERSION - # be sure to push the named tag explicitly; you don't want to push any other local tags! - git push origin $RELEASE_VERSION - ``` - - **GitHub permissions**: `git push` will only work if you have the - `admin` GitHub permission on the cert-manager repo to create or push to - the branch, see [prerequisites](#prerequisites). If you do not have this - permission, you will have to open a PR to merge master into the release - branch), and wait for the PR checks to become green. - - For recent versions of cert-manager, the tag being pushed will trigger a Google Cloud Build job to start, - kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to - the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). - -6. Generate and edit the release notes: - - 1. Use the following two tables to understand how to fill in the four - environment variables needed for the next step. These four environment - variables are documented on the - [README](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md#options) - for the Kubernetes `release-notes` tool. - - | Variable | Description | - | ----------------- | --------------------------------------- | - | `RELEASE_VERSION` | The git tag | - | `START_TAG`\* | The git tag of the "previous"\* release | - | `END_REV` | Name of your release branch (inclusive) | - | `BRANCH` | Name of your release branch | - - Examples for each release type (e.g., initial alpha release): - - | Variable | Example 1 | Example 2 | Example 2 | Example 3 | Example 4 | - | ----------------- | ---------------- | ---------------- | ---------------- | ------------- | ------------- | - | | | | | | | - | | initial alpha | subsequent alpha | beta release | final release | patch release | - | `RELEASE_VERSION` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.3.0-beta.0` | `v1.3.0` | `v1.3.1` | - | `START_TAG`\* | `v1.2.0` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.2.0`\*\* | `v1.3.0` | - | `END_REV` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | - | `BRANCH` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | - - > \*The git tag of the "previous" release (`START_TAG`) depends on which - > type of release you count on doing. Look at the above examples to - > understand a bit more what those are. - - > \*\*Do not use a patch here (e.g., no `v1.2.3`). It must be `v1.2.0`: - > you must use the latest tag that belongs to the release branch you are - > releasing on; in the above example, the release branch is - > `release-1.3`, and the latest tag on that branch is `v1.2.0`. - - After finding out the value for each of the 4 environment variables, set - the variables in your shell (for example, following the example 1): - - ```bash - export RELEASE_VERSION="v1.3.0-alpha.0" - export START_TAG="v1.2.0" - export END_REV="release-1.3" - export BRANCH="release-1.3" - ``` - - 2. Generate `release-notes.md` at the root of your cert-manager repo folder - with the following command: - - ```bash - # Must be run from the cert-manager folder. - export GITHUB_TOKEN=*your-token* - git fetch origin $BRANCH - export START_SHA="$(git rev-list --reverse --ancestry-path $(git merge-base $START_TAG $BRANCH)..$BRANCH | head -1)" - release-notes --debug --repo-path cert-manager \ - --org cert-manager --repo cert-manager \ - --required-author "jetstack-bot" \ - --output release-notes.md - ``` - -

              - The GitHub token **does not need any scope**. The token is required - only to avoid rate-limits imposed on anonymous API users. -

              - - 3. Replace the GitHub issue numbers and GitHub handles (e.g., `#1234` or - `@maelvls`) with actual links: - - ```bash - sed release-notes.md \ - -e 's$#([0-9]+)$[#\1](https://github.com/cert-manager/cert-manager/pull/\1)$g' \ - -e 's$@(\w+)$[@\1](https://github.com/\1)$g' \ - -E \ - -i - ``` - - 4. Sanity check the notes: - - - Make sure the notes contain details of all the features and bug - fixes that you expect to be in the release. - - Add additional blurb, notable items and characterize change log. - - You can see the commits that will go into this release by using the - [GitHub compare](https://github.com/cert-manager/cert-manager/compare). For - example, while releasing `v1.0.0`, you want to compare it with the - latest pre-released version `v1.0.0-beta.1`: - - ```text - https://github.com/cert-manager/cert-manager/compare/v1.0.0-beta.1...master - ``` - - 4. **(final release only)** Check the release notes include all changes - since the last final release. - -7. Check that the build is complete and send Slack messages about the release: - - 1. For recent versions of cert-manager, the build will have been automatically - triggered by the tag being pushed earlier. You can check if it's complete on - the [GCB Build History](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). - - 2. If you're releasing an older version of cert-manager (earlier than 1.10) then the automatic build - will failed because the GCB config for that build wasn't backported. - In this case, you'll need to trigger the build manually using `cmrel`, which takes about 5 minutes: - - ```bash - # Must be run from the "cert-manager/release" repo folder. - cmrel makestage --ref=$RELEASE_VERSION - ``` - - This build takes ~5 minutes. It will build all container images and create - all the manifest files, sign Helm charts and upload everything to a storage - bucket on Google Cloud. These artifacts will then be published and released - in the next steps. - - 3. In any case, send a first Slack message to `#cert-manager-dev`: - -

              - Releasing 1.2.0-alpha.2 🧵 -

              - -

              - 🔰 Please have a quick look at the build log as it might contain some unredacted - data that we forgot to hide. We try to make sure the sensitive data is - properly redacted but sometimes we forget to update this. -

              - - 3. Send a second Slack message in reply to this first message with the - Cloud Build job link. For example, the message might look like: - -

              - Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237 -

              - -8. Run `cmrel publish`: - - 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are - valid. Run the following command: - - ```bash - # Must be run from the "cert-manager/release" repo folder. - cmrel publish --release-name "$RELEASE_VERSION" - ``` - - You can view the progress by clicking the Google Cloud Build URL in the - output of this command. - - 2. While the build is running, send a third Slack message in reply to the first message: - -

              - Follow the `cmrel publish` dry-run build: https://console.cloud.google.com/cloud-build/builds16f6f875-0a23-4fff-b24d-3de0af207463?project=1021342095237 -

              - - 3. Now publish the release artifacts for real. The following command will publish the artifacts to GitHub, `Quay.io` and to our - [helm chart repository](https://charts.jetstack.io): - - ```bash - # Must be run from the "cert-manager/release" repo folder. - cmrel publish --nomock --release-name "$RELEASE_VERSION" - ``` - -
              - ⏰ Upon completion there will be: -
                -
              1. - A draft release of cert-manager on GitHub -
              2. -
              3. - A pull request containing the new Helm chart -
              4. -
              -
              - - 4. While the build is running, send a fourth Slack message in reply to the first message: - -

              - Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237 -

              - -9. Publish the GitHub release: - - 1. Visit the draft GitHub release and paste in the release notes that you - generated earlier. You will need to manually edit the content to match - the style of earlier releases. In particular, remember to remove - package-related changes. - - 2. **(initial alpha, subsequent alpha and beta only)** Tick the box "This is - a pre-release". - - 3. **(final release and patch release)** Tick the box "Set as the latest - release". - - 4. Click "Publish" to make the GitHub release live. - -10. Merge the pull request containing the Helm chart: - - The Helm charts for cert-manager are served using Cloudflare pages - and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). - The `cmrel publish --nomock` step (above) will have created a PR in this repository which you now have to review and merge, as follows: - - 1. [Visit the pull request](https://github.com/jetstack/jetstack-charts/pulls) - 2. Review the changes - 3. Fix any failing checks - 4. Merge the PR - 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). - -11. **(final release only)** Add the new final release to the - [supported-releases](../installation/supported-releases.md) page. - -12. Open a PR for a [Homebrew](https://brew.sh) formula update for `cmctl`. - - Assuming you have `brew` installed, you can use the `brew bump-formula-pr` - command to do this. You'll need the new tag name and the commit hash of that - tag. See `brew bump-formula-pr --help` for up to date details, but the command - will be of the form: - - ```bash - brew bump-formula-pr --dry-run --tag v0.10.0 --revision da3265115bfd8be5780801cc6105fa857ef71965 cmctl - ``` - - Replacing the tag and revision with the new ones. - - This will take time for the Homebrew team to review. Once the pull reqeust - against https://github.com/homebrew/homebrew-core has been opened, continue - with further release steps. - -13. Post a Slack message as an answer to the first message. Toggle the check - box "Also send to `#cert-manager-dev`" so that the message is well - visible. Also cross-post the message on `#cert-manager`. - -

              - https://github.com/cert-manager/cert-manager/releases/tag/v1.0.0 🎉 -

              - -14. **(final release only)** Show the release to the world: - - 1. Send an email to - [`cert-manager-dev@googlegroups.com`](https://groups.google.com/g/cert-manager-dev) - with the `release` label - ([examples](https://groups.google.com/g/cert-manager-dev?label=release)). - - 2. Send a tweet on the cert-manager Twitter account! Login details are in Jetstack's 1password (for now). - ([Example tweet](https://twitter.com/CertManager/status/1612886311957831680)). Make sure [@JetstackHQ](https://twitter.com/JetstackHQ) retweets it! - - 3. Send a toot from the cert-manager Mastodon account! Login details are in Jetstack's 1password (for now). - ([Example toot](https://infosec.exchange/@CertManager/109666434738850493)) - -15. Proceed to the post-release steps: - - 1. **(initial beta only)** Create a PR on - [cert-manager/release](https://github.com/cert-manager/release) in order to - add the new release to our list of periodic ProwJobs. Use [this PR](https://github.com/cert-manager/testing/pull/774/) as an example. - - 2. **(initial beta only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and - open a PR to [cert-manager/testing](https://github.com/cert-manager/testing) adding the generated prow configs. - Use [this PR](https://github.com/cert-manager/testing/pull/766) as an example. - - 3. If needed, open a PR to - [`cert-manager/website`](https://github.com/cert-manager/website) in - order to: - - - Update the section "How we determine supported Kubernetes versions" on - the [supported-releases](../installation/supported-releases.md) page. - - Add any new release notes, if needed. - - 4. **(final release only)** Create a PR on - [cert-manager/release](https://github.com/cert-manager/release), - removing the now unsupported release version (2 versions back) in this file: - - ```plain - prowspecs/specs.go - ``` - - This will remove the periodic ProwJobs for this version as they're no longer needed. - - 5. **(final release only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and - open a PR to [jetstack/testing](https://github.com/cert-manager/testing) adding the generated prow configs. - - 6. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/cert-manager/testing) - and update the [milestone_applier](https://github.com/cert-manager/testing/blob/3110b68e082c3625bf0d26265be2d29e41da14b2/config/plugins.yaml#L69) - config so that newly raised PRs on master are applied to a new milestone - for the next release. E.g. if master currently points at the `v1.10` milestone, change it to point at `v1.11`. - - If the [milestone](https://github.com/cert-manager/cert-manager/milestones) for the next release doesn't exist, - create it first. If you consider the milestone for the version you just released to be complete, close it. - - 7. **(final release only)** Open a PR to - [`cert-manager/website`](https://github.com/cert-manager/website) in - order to: - - - Update the section "Supported releases" in the - [supported-releases](../installation/supported-releases.md) page. - - Update the section "How we determine supported Kubernetes versions" on - the [supported-releases](../installation/supported-releases.md) page. - In the table, set "n/a" for the line where "next periodic" is since - these tests will be disabled until we do our first alpha. - - Update the [API docs](../reference/api-docs.md) and [CLI docs](../cli/README.md) by running `scripts/gendocs/generate` - and commit any changes to a branch and create a PR to merge those into - `master` or `release-next` depending on whether this is a minor or - patch release. - - 8. Ensure that any installation commands in - [`cert-manager/website`](https://github.com/cert-manager/website) install - the latest version. This should be done after every release, including - patch releases as we want to encourage users to always install the latest - patch. In addition, ensure that release notes for the latest version are added. - - 9. Open a PR against the Krew index such as [this one](https://github.com/kubernetes-sigs/krew-index/pull/1724), - bumping the versions of our kubectl plugins. This is likely only worthwhile if - cmctl / kubectl plugin functionality has changed significantly or after the first release of a new major version. - - 10. Create a new OLM package and publish to OperatorHub - - cert-manager can be [installed](https://cert-manager.io/docs/installation/operator-lifecycle-manager/) using Operator Lifecycle Manager (OLM) - so we need to create OLM packages for each cert-manager version and publish them to both - [`operatorhub.io`](https://operatorhub.io/operator/cert-manager) and the equivalent package index for RedHat OpenShift. - - Follow [the cert-manager OLM release process](https://github.com/cert-manager/cert-manager-olm#release-process) and, once published, - [verify that the cert-manager OLM installation instructions](https://cert-manager.io/docs/installation/operator-lifecycle-manager/) still work. - -## Older Releases - -The above guide only applies for versions of cert-manager from v1.8 and newer. - -Older versions were built using Bazel and this difference in build process is reflected in the release process. - -### cert-manager 1.6 and 1.7 - -Follow [this older version][older-release-process] of the release process on GitHub, rather than the guide on this website. - -The most notable difference is you'll call `cmrel stage` rather than `cmrel makestage`. You should be fine to use the latest -version of `cmrel` to do the release. - -### cert-manager 1.5 and earlier - -If you're releasing version 1.5 or earlier you must also be sure to install a different version of `cmrel`. - -In the step where you install `cmrel`, you'll want to run the following instead: - -```bash -go install github.com/cert-manager/release/cmd/cmrel@cert-manager-pre-1.6 -``` - -This will ensure that the version of `cmrel` you're using is compatible with the version of cert-manager you're releasing. - -In addition, when you check out the `cert-manager/release` repository you should be sure to check out the `cert-manager-pre-1.6` tag in that repo: - -```bash -git checkout cert-manager-pre-1.6 -``` - -Other than the different `cert-manager/release` tag and `cmrel` version, you can follow the [same older release documentation][older-release-process] as -is used for 1.6 and 1.7 - just remember to change the version of `cmrel` you install! - -[older-release-process]: https://github.com/cert-manager/website/blob/6fa0db74de0ae17d7be638a08155d1b4e036aaa9/content/en/docs/contributing/release-process.md?plain=1 diff --git a/content/v1.12-docs/contributing/security.md b/content/v1.12-docs/contributing/security.md deleted file mode 100644 index 6d33165ede..0000000000 --- a/content/v1.12-docs/contributing/security.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Reporting Security Issues -description: 'cert-manager contributing: Security policy' ---- - -Security is the number one priority for cert-manager. If you think you've -found a vulnerability in any cert-manager project, please follow the -[vulnerability reporting process](https://github.com/cert-manager/cert-manager/blob/master/SECURITY.md) -documented in the main cert-manager repository. - -The reporting process is the same for all repositories under the -cert-manager organization. The process is documented in one place to ensure -a single source of truth and a single list of [security contacts](https://github.com/cert-manager/cert-manager/blob/master/SECURITY_CONTACTS.md). \ No newline at end of file diff --git a/content/v1.12-docs/contributing/sign-off.md b/content/v1.12-docs/contributing/sign-off.md deleted file mode 100644 index 2e0dd8cbfa..0000000000 --- a/content/v1.12-docs/contributing/sign-off.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: DCO Sign Off -description: 'cert-manager contributing: DCO Sign-off' ---- - -All contributors to the project retain copyright to their work, but must only submit -work which they have the rights to submit. - -We require all contributors to acknowledge that they have the rights to the code they're contributing -by signing their commits in git using a "DCO Sign Off". Note that this is different to "commit signing" -using something like PGP or [`gitsign`](https://github.com/sigstore/gitsign)! - -Any copyright notices in a cert-manager repo should specify the authors as -"The cert-manager Authors". - -To sign your work, pass the `--signoff` option to `git commit` or `git rebase`: - -```bash -# Sign off a commit as you're making it -git commit --signoff -m"my commit" - -# Add a signoff to the last commit you made -git commit --amend --signoff - -# Rebase your branch against master and sign off every commit in your branch -git rebase --signoff master -``` - -This will add a line similar to the following at the end of your commit: - -```text -Signed-off-by: Joe Bloggs -``` - -By signing off a commit you're stating that you certify the following: - -```text -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -1 Letterman Drive -Suite D4700 -San Francisco, CA, 94129 - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` - -That statement is taken from [https://developercertificate.org/](https://developercertificate.org/). diff --git a/content/v1.12-docs/contributing/signing-keys.md b/content/v1.12-docs/contributing/signing-keys.md deleted file mode 100644 index 74d5382d86..0000000000 --- a/content/v1.12-docs/contributing/signing-keys.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Signing Keys -description: 'cert-manager contributing: Code signing / Signing keys' ---- - -This page describes the bootstrapping process for a key, including how to do it and why a bootstrapping -process is required. - -## What do we Serve? - -To facilitate verification of signatures, we serve public key information from the cert-manager website -directly. It's important to serve the keys from a different location to where the artifacts are hosted; if the -keys were hosted at the same location as the artifacts, an attacker able to change the artifacts would be able -to also change the keys! - -We serve several key types under `static/public-keys`: - -- `cert-manager-pgp-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.asc`: ASCII-armored PGP public key, used for verifying signatures on helm charts via `helm verify` (after being converted to a keyring) -- `cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg`: Old style GPG keyring, needed by the `--keyring` parameter to `helm verify`. See Keyring below. -- `cert-manager-pubkey-2021-09-20.pem`: The raw, PEM-encoded public key used for signing. Cannot be used with GPG (and therefore helm), but should be used for other verification types. - -## Background / Architecture - -Code signing for cert-manager artifacts is done entirely using cloud KMS keys, to ensure that nobody -can get access to the private keys in plain-text; all signing operations using the key are therefore -done through cloud APIs and are logged. - -Currently, all keys are on Google KMS, since the rest of cert-manager's release infrastructure is also -in GCP. The key - and the role bindings which allow access to it - are specified in terraform in a closed -source Jetstack repo. - -## Why Bootstrap? - -While the private key is not retrievable for a KMS key, the public key is and _must_ be retrieved so that -end-users can verify signatures made by the key. In GCP, retrieving the public key is itself an -[API call](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys.cryptoKeyVersions/getPublicKey) -which returns the raw key in a PEM encoded format. - -That PEM-encoded public key works for some cases (e.g. verifying container signature made using `cosign`) but -it's not sufficient for Helm chart verification, since Helm chart signing (sadly) requires the use of PGP. - -## Bootstrapping a PGP Identity - -It's possible to use a shim to use GCP KMS as a PGP key which enables us to avoid having two separate signing keys, -but PGP public identities are slightly more complicated than plain public keys; they also contain a name, -creation time, comment and email address to identify the signer. This public "identity" must itself be signed by the -private key (to prove that the information in the identity is legitimate). - -This bootstrapping can be done using the cert-manager release tool, `cmrel`: - -```console -# note that the key name might not exactly match this in the future -$ cmrel bootstrap-pgp --key "projects/cert-manager-release/locations/europe-west1/keyRings/cert-manager-release/cryptoKeys/cert-manager-release-signing-key/cryptoKeyVersions/1" -``` - -This will trigger a cloud build job which will output both the armored PGP identity and the raw PEM public key; the values -can be copied from the job output. - -### GPG Keyring - -As an additional UX feature, we can also generate a GPG keyring from the PGP identity, since the keyring is what's required -by the Helm CLI to actually validate a chart: - -```console -# Example of verifying a chart. -$ helm verify --keyring cert_manager_keyring_1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg /path/to/chart.tgz -Signed by: cert-manager Maintainers -Using Key With Fingerprint: 1020.... -Chart Hash Verified: sha256:bb86... -``` - -The keyring can be generated using [this script](https://github.com/cert-manager/release/blob/a219e18b2e64ef078bf73b3641d589b43d1fccb8/hack/helm_keyring.sh). \ No newline at end of file diff --git a/content/v1.12-docs/contributing/third-party-code-donation.md b/content/v1.12-docs/contributing/third-party-code-donation.md deleted file mode 100644 index f53da8e3c6..0000000000 --- a/content/v1.12-docs/contributing/third-party-code-donation.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: Donating Third Party Code to cert-manager -description: 'cert-manager contributing: Third party code donations' ---- - -The cert-manager project welcomes external contributions and has benefited greatly from thousands -of commits from hundreds of different contributors. Most code is usually committed through pull -requests to a specific repo, whether that be the main cert-manager repository or one of the associated -repositories such as the website. - -Some contributions aren't as well suited to that kind of workflow, however. That would most likely -be because their functionality doesn't belong in any particular existing cert-manager repo, while still -relating to the cert-manager project. - -This document aims to address the donation of code to the cert-manager project, and to provide a -framework for sustainable contributions which can be tested and relied upon going forwards by both -cert-manager maintainers and users. - -The requirements in this document are based in part on what's done for CoreDNS, Envoy, Kubernetes -and containerd. - -## Requirements - -1. Code must be licensed appropriately, including any dependencies - We'd prefer [Apache 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) since that's - what cert-manager [uses](https://github.com/cert-manager/cert-manager/blob/master/LICENSE), but the - license must be [OSI approved](https://opensource.org/licenses). -2. Code must conform to CNCF standards and due diligence requirements - You don't need to go over this with a fine-toothed comb; the intent here is that no code donation - should have a negative effect on cert-manager's progress as a CNCF project. See the - [CNCF due diligence template](https://github.com/cncf/toc/blob/main/process/dd-review-template.md) -3. Must be sponsored by an existing maintainer - An existing regular contributor to cert-manager must sponsor the adoption of any third party code - donation. This ensures that there's a single point of contact for the party donating the code. -4. Must pass cert-manager conformance tests - This might not apply to all donations, but where conformance tests exist any donated code must - pass them. E.g. for [external issuers](https://github.com/cert-manager/cert-manager/blob/dffbf391dbb0fc6c1cfea62e561a9c6f54362ab0/test/e2e/suite/conformance/certificates/external/external.go#L41-L62) -5. Must provide a point-of-contact for questions about the project for at least 3 months after acceptance - We don't anticipate that we'd need to reach out often after the donation has been accepted, - but it's important to have someone we can reach out to if we need to. -6. The donation must be a defined extension type or justify why it doesn't belong in the main repositories - E.g. an ACME DNS solver, a custom issuer or an ACME HTTP solver -7. Code must have a similar level of quality to cert-manager itself - This could be enforced by, for example, running static analysis tools on the code base similar to - those used by cert-manager. -8. Code must have a non-trivial test suite, including both unit tests and end-to-end tests - These tests must be able to be run in their entirety after a PR is raised against the repo. We don't - need 100% code coverage, but there should be tests for important functionality. -9. The project must adopt the cert-manager security policy and link back to the policy, as in e.g. - the [istio-csr `SECURITY.md`](https://github.com/cert-manager/istio-csr/blob/master/SECURITY.md) -10. Must have DCO sign-offs or coverage for all commits - To ensure that all code can legally be donated, all commits should have DCO sign-off or else have - a positive affirmation made by each contributor prior to donation. See below. - -## Preferences - -These items are not absolutely necessary but they definitely help if a code donation is to be accepted. - -- Should be written in Go - We don't _need_ code to be written in Go, but we'd much prefer that it is. Since cert-manager itself - is written in Go, code donations in Go allow us to use existing experience and tooling on Go code. - -## DCO Signoff - -As a method of ensuring that the donator has permission to donate the code, we require DCO sign-offs - -or something equivalent - to be in place at the time of the donation. - -The cert-manager [DCO signoff process](https://cert-manager.io/docs/contributing/sign-off/) -would be appropriate. Existing contributors could bootstrap this process by creating an empty signed-off -with a note that previous code should be considered signed off as of that commit: - -```bash -git commit --allow-empty --signoff --message="bootstrapping DCO signoff for past commits" -``` - -## After Donation - -Code files in the donated repository must be updated to include the relevant -[cert-manager boilerplate](https://github.com/cert-manager/cert-manager/blob/master/hack/boilerplate/boilerplate.go.txt) \ No newline at end of file diff --git a/content/v1.12-docs/installation/supported-releases.md b/content/v1.12-docs/installation/supported-releases.md deleted file mode 100644 index 54b75d4c33..0000000000 --- a/content/v1.12-docs/installation/supported-releases.md +++ /dev/null @@ -1,289 +0,0 @@ ---- -title: Supported Releases -description: Supported releases, Kubernetes versions, OpenShift versions and upcoming release timeline ---- - -{/* -Inspired by https://istio.io/latest/about/supported-releases/ -*/} - -This page lists the status, timeline and policy for currently supported releases. - -Each release is supported for a period of four months, and we aim to create a new -release roughly every two months, accounting for holiday periods, major conferences -and other world events. - -cert-manager expects that ServerSideApply is enabled in the cluster for all version of Kubernetes from 1.24 and above. - -

              Currently supported releases

              - -| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| -| [1.12][] | May 19, 2024 | End of September, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | -| [1.12.0] | Jan 11, 2023 | Release of 1.13 | 1.21 → 1.26 | 4.8 → 4.13 | - -\*ServerSideApply should be enabled in the cluster - -## Upcoming releases - -| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:----------------------:|:---------------------:|:----------------------------------:|:---------------------------------:| -| [1.13][] | End of September, 2024 | End of November, 2024 | 1.22 → 1.27 | 4.9 → 4.14 | - -Dates in the future are uncertain and might change. - -## Old releases - -| Release | Release Date | EOL | Compatible Kubernetes versions | Compatible OpenShift versions | -|----------|:------------:|:------------:|:------------------------------:|:-----------------------------:| -| [1.10][] | Oct 17, 2022 | May 19, 2024 | 1.20 → 1.26 | 4.7 → 4.13 | -| [1.9][] | Jul 22, 2022 | Jan 11, 2023 | 1.20 → 1.24 | 4.7 → 4.11 | -| [1.8][] | Apr 05, 2022 | Oct 17, 2022 | 1.19 → 1.24 | 4.6 → 4.11 | -| [1.7][] | Jan 26, 2021 | Jul 22, 2022 | 1.18 → 1.23 | 4.5 → 4.9 | -| [1.6][] | Oct 26, 2021 | Apr 05, 2022 | 1.17 → 1.22 | 4.4 → 4.9 | -| [1.5][] | Aug 11, 2021 | Jan 26, 2022 | 1.16 → 1.22 | 4.3 → 4.8 | -| [1.4][] | Jun 15, 2021 | Oct 26, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | -| [1.3][] | Apr 08, 2021 | Aug 11, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | -| [1.2][] | Feb 10, 2021 | Jun 15, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | -| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.12.0 1.21 | 3.11 → 4.7 | -| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.12.0 1.21 | 3.11 → 4.7 | -| [0.11][] | Oct 10, 2019 | Jan 21, 2020 | 1.9 → 1.21 | 3.09 → 4.7 | - -[s]: #kubernetes-supported-versions -[1.13]: https://github.com/cert-manager/cert-manager/milestone/34 -[1.12]: https://cert-manager.io/docs/release-notes/release-notes-1.12 -[1.12.0 https://cert-manager.io/docs/release-notes/release-notes-1.11 -[1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 -[1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 -[1.8]: https://cert-manager.io/docs/release-notes/release-notes-1.8 -[1.7]: https://cert-manager.io/docs/release-notes/release-notes-1.7 -[1.6]: https://cert-manager.io/docs/release-notes/release-notes-1.6 -[1.5]: https://cert-manager.io/docs/release-notes/release-notes-1.5 -[1.4]: https://cert-manager.io/docs/release-notes/release-notes-1.4 -[1.3]: https://cert-manager.io/docs/release-notes/release-notes-1.3 -[1.2]: https://cert-manager.io/docs/release-notes/release-notes-1.2 -[1.1]: https://cert-manager.io/docs/release-notes/release-notes-1.1 -[1.0]: https://cert-manager.io/docs/release-notes/release-notes-1.0 -[0.16]: https://cert-manager.io/docs/release-notes/release-notes-0.16 -[0.15]: https://cert-manager.io/docs/release-notes/release-notes-0.15 -[0.14]: https://cert-manager.io/docs/release-notes/release-notes-0.14 -[0.13]: https://cert-manager.io/docs/release-notes/release-notes-0.13 -[0.12]: https://cert-manager.io/docs/release-notes/release-notes-0.12 -[0.11]: https://cert-manager.io/docs/release-notes/release-notes-0.11 - -We list cert-manager releases on [GitHub](https://github.com/cert-manager/cert-manager/releases), -and release notes on [cert-manager.io](https://cert-manager.io/docs/release-notes/). - -We also maintain detailed [upgrade instructions](https://cert-manager.io/docs/installation/upgrading/). - -## Support policy - -### What we mean by support - -Our support window is four months for each release branch. In the below -diagram, `release-1.2` is an example of a release branch. The support -window corresponds to the two latest releases, given that we produce a new -final release every two months. We offer two types of support: - -- [Technical support](#technical-support), -- [Security and bug fixes](#bug-fixes-support). - -For example, imagining that the latest release is `v1.2.0`, you can expect -support for both `v1.2.0` and `v1.1.0`. Only the last patch release of each -branch is actually supported. - -```diagram - v1.0.0 ^ - Sep 2, 2020 | UNSUPPORTED -------+---------------------------------------------> release-1.0 | RELEASES - \ v - \ - \ v1.1.0 - \ Nov 24, 2020 ^ - ---------+-------------------------------> release-1.1 | - \ | SUPPORTED - \ | RELEASES - \ v1.2.0 | = the two - \ Feb 10, 2021 | last - ------------+--------------> release-1.2 | releases - \ v - \ - \ - \ - -----------> master branch - April 1, 2021 -``` - -

              Technical support

              - -Technical assistance is offered on a best-effort basis for supported -releases only. You can request support from the community on [Kubernetes -Slack](https://slack.k8s.io/) (in the `#cert-manager` channel), using -[GitHub Discussions][discussions] or using the [cert-manager-dev][group] -Google group. - -[discussions]: https://github.com/cert-manager/cert-manager/discussions -[group]: https://groups.google.com/g/cert-manager-dev - -

              Security and bug fixes

              - -We back-port important bug fixes — including security fixes — to all -currently supported releases. - -- [Security issues](#security-issues), -- [Critical bugs](#critical-bugs), -- [Long-standing bugs](#long-standing-bugs). - -

              Security issues

              - -**Security issues** are fixed as soon as possible. They get back-ported to -the last two releases, and a new patch release is immediately created for them. - -

              Critical bugs

              - -**Critical bugs** include both regression bugs as well as upgrade bugs. - -Regressions are functionalities that worked in a previous release but no longer -work. [#4142][], [#3393][] and [#2857][] are three examples of regressions. - -Upgrade bugs are issues (often Helm-related) preventing users from -upgrading to currently supported releases from earlier releases of -cert-manager. [#3882][] and [#3644][] are examples of upgrade bugs. - -Note that [intentional breaking changes](#breaking-changes) do not belong to -this category. - -Fixes for critical bugs are (usually) immediately back-ported by creating a new -patch release for the currently supported releases. - -

              Long-standing bugs

              - -**Long-standing bug**: sometimes a bug exists for a long time, and may have -known workarounds. [#3444][] is an example of a long-standing bug. - -Where we feel that back-porting would be difficult or might be a stability -risk to clusters running cert-manager, we'll make the fix in a major -release but avoid back-porting the fix. - -

              Breaking changes

              - -Breaking changes are changes that intentionally break the cert-manager -Kubernetes API or the command line flags. We avoid making breaking changes -where possible, and where they're required we'll give as much notice as -possible. - -

              Other back-ports

              - -We aim to be conservative in what we back-port. That applies especially for anything which -could be a _runtime_ change - that is, a change which might alter behavior for someone -upgrading between patch releases. - -That means that if a candidate for back-porting has a chance of having a runtime impact we're -unlikely to accept the change unless it addresses a security issue or a critical bug. - -We reserve the right to back-port other changes which are unlikely to have a runtime impact, such as -documentation or tooling changes. An example would be [#5209][] which updated how we perform a release of -cert-manager but didn't have any realistic chance of having a runtime impact. - -Generally we'll seek to be pragmatic. A rule of thumb might be to ask: - -"Does this back-port improve cert-manager, bearing in mind that we really value stability for already-released versions?" - -[#3393]: https://github.com/cert-manager/cert-manager/issues/3393 "Broken CloudFlare DNS01 challenge" -[#2857]: https://github.com/cert-manager/cert-manager/issues/2857 "CloudDNS DNS01 challenge crashes cert-manager" -[#4142]: https://github.com/cert-manager/cert-manager/issues/4142 "Cannot issue a certificate that has the same subject and issuer" -[#3444]: https://github.com/cert-manager/cert-manager/issues/3444 "Certificates do not get immediately updated after updating them" -[#3882]: https://github.com/cert-manager/cert-manager/pull/3882 "Certificate's revision history limit validated by webhook" -[#3644]: https://github.com/cert-manager/cert-manager/issues/3644 "Helm upgrade from v1.2 to v1.2 impossible due to a Helm bug" -[#5209]: https://github.com/cert-manager/cert-manager/pull/5209 "release-1.8: rclone" - - -

              How we determine supported Kubernetes versions

              - -The list of supported Kubernetes versions displayed in the [Supported Releases](#supported-releases) section -depends on what the cert-manager maintainers think is reasonable to support and to test. - -In practice, this is largely determined based on what versions of [kind](https://github.com/kubernetes-sigs/kind) -are available for testing, and which versions of Kubernetes are provided by major upstream cloud Kubernetes vendors -including EKS, GKE, AKS and OpenShift. - -| Vendor | Oldest Kubernetes Release\* | Other Older Kubernetes Releases | -|:-----------------:|-----------------------------|------------------------------------------------------------------------------------| -| [EKS][eks] | 1.22 (EOL Jun 2023) | 1.23 (EOL Oct 2023), 1.24 (EOL Jan 2024), 1.25 (EOL May 2024), 1.26 (EOL Jun 2024) | -| [GKE][gke] | 1.23 (EOL Jul 2023) | 1.24 (EOL Oct 2023), 1.25 (EOL Feb 2024), 1.26 (EOL May 2024), 1.27 (EOL Jan 2025) | -| [AKS][aks] | 1.24 (EOL Jul 2023) | 1.25 (EOL Dec 2023), 1.26 (EOL Mar 2024), 1.27 (EOL Jun 2024) | -| [OpenShift 4][os] | 1.22 (4.9, EOL Jun 2023) | 1.23 (4.10, EOL Oct 2023), 1.24 (4.11, EOL Feb 2024), 1.25 (4.12, EOL Jan 2025) | - -\*Oldest release relevant to the next cert-manager release, as of 2023-05-19 - -[eks]: https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html#kubernetes-release-calendar -[gke]: https://cloud.google.com/kubernetes-engine/docs/release-schedule -[aks]: https://docs.microsoft.com/en-us/azure/aks/supported-kubernetes-versions#aks-kubernetes-release-calendar -[os]: https://access.redhat.com/support/policy/updates/openshift#dates - -### OpenShift - -cert-manager supports versions of OpenShift 4 based on the version of Kubernetes -that each version maps to. - -For convenience, the following table shows these version mappings: - -| OpenShift versions | Kubernetes version | -|--------------------|--------------------| -| 4.14 | 1.27 | -| 4.13 | 1.26 | -| 4.12 | 1.25 | -| 4.11 | 1.24 | -| 4.10, 4.10 EUS | 1.23 | -| 4.9 | 1.22 | -| 4.8, 4.8 EUS | 1.21 | -| 4.7 | 1.20 | -| 4.6, 4.6 EUS | 1.19 | - -Note that some OpenShift versions listed above may be predicted, since an updated version of OpenShift may -not yet be available for the latest Kubernetes releases. - -The last version of cert-manager to support OpenShift 3 was cert-manager 1.2, which is -no longer maintained. - -## Terminology - -The term "release" (or "minor release") refers to one minor version of -cert-manager. For example, 1.2 and 1.3 are two releases. Note that we do -not use the prefix `v` for releases (just "1.2"). This is because releases -are not used as git tags. - -Patch releases use the `v` prefix (e.g., `v1.2.0`, `v1.3.1`...) since one -patch release = one git tag. The initial patch release is called "final -release": - -| Type of release | Example of git tag | Corresponding release | Corresponding release branch\* | -| --------------- | ------------------ | --------------------- | ------------------------------ | -| Final release | `v1.3.0` | 1.3 | `release-1.3` | -| Patch release | `v1.3.1` | 1.3 | `release-1.3` | -| Pre-release | `v1.4.0-alpha.0` | N/A\*\* | `release-1.4` | - -\*For maintainers: each release has an associated long-lived branch that we -call the “release branch”. For example, `release-1.2` is the release branch -for release 1.2. - -\*\*Pre-releases (e.g., `v1.3.0-alpha.0`) don't have a corresponding -release (e.g., 1.3) since a release only exists after a final release -(e.g., `v1.3.0`) has been created. - -Our naming scheme mostly follows [Semantic Versioning -2.0.0](https://semver.org/) with `v` prepended to git tags and docker -images: - -```plain -v.. -``` - -where `` is increased for each release, and `` counts the -number of patches for the current `` release. A patch is usually a -small change relative to the `` release. diff --git a/content/v1.12-docs/manifest.json b/content/v1.12-docs/manifest.json index 5bcb8ddf36..4d84895861 100644 --- a/content/v1.12-docs/manifest.json +++ b/content/v1.12-docs/manifest.json @@ -201,53 +201,6 @@ } ] }, - { - "title": "Projects", - "routes": [ - { - "title": "Contents", - "path": "/v1.12-docs/projects/README.md" - }, - { - "title": "istio-csr", - "path": "/v1.12-docs/projects/istio-csr.md" - }, - { - "title": "csi-driver", - "path": "/v1.12-docs/projects/csi-driver.md" - }, - { - "title": "csi-driver-spiffe", - "path": "/v1.12-docs/projects/csi-driver-spiffe.md" - }, - { - "title": "approver-policy", - "routes": [ - { - "title": "Introduction", - "path": "/v1.12-docs/projects/approver-policy/README.md" - }, - { - "title": "API Reference", - "path": "/v1.12-docs/projects/approver-policy/api-reference.md" - } - ] - }, - { - "title": "trust-manager", - "routes": [ - { - "title": "Introduction", - "path": "/v1.12-docs/projects/trust-manager/README.md" - }, - { - "title": "API Reference", - "path": "/v1.12-docs/projects/trust-manager/api-reference.md" - } - ] - } - ] - }, { "title": "Tutorials", "routes": [ @@ -326,101 +279,6 @@ "title": "FAQ", "path": "/v1.12-docs/faq/README.md" }, - { - "title": "Contributing", - "routes": [ - { - "title": "Introduction", - "path": "/v1.12-docs/contributing/README.md" - }, - { - "title": "Feature Policy", - "path": "/v1.12-docs/contributing/policy.md" - }, - { - "title": "Building cert-manager", - "path": "/v1.12-docs/contributing/building.md" - }, - { - "title": "Contributing Flow", - "path": "/v1.12-docs/contributing/contributing-flow.md" - }, - { - "title": "CRDs", - "path": "/v1.12-docs/contributing/crds.md" - }, - { - "title": "DNS Providers", - "path": "/v1.12-docs/contributing/dns-providers.md" - }, - { - "title": "Running End-to-End Tests", - "path": "/v1.12-docs/contributing/e2e.md" - }, - { - "title": "Implementing External Issuers", - "path": "/v1.12-docs/contributing/external-issuers.md" - }, - { - "title": "DCO Sign Off", - "path": "/v1.12-docs/contributing/sign-off.md" - }, - { - "title": "Release Process", - "path": "/v1.12-docs/contributing/release-process.md" - }, - { - "title": "Developing with Kind", - "path": "/v1.12-docs/contributing/kind.md" - }, - { - "title": "Implementing Feature Gates", - "path": "/v1.12-docs/contributing/featuregates.md" - }, - { - "title": "Google Season of Docs", - "routes": [ - { - "title": "Introduction", - "path": "/v1.12-docs/contributing/google-season-of-docs/README.md" - }, - { - "title": "2022", - "routes": [ - { - "title": "Introduction", - "path": "/v1.12-docs/contributing/google-season-of-docs/2022/README.md" - }, - { - "title": "Improve the Navigation and Structure of the cert-manager Website", - "path": "/v1.12-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md" - } - ] - } - ] - }, - { - "title": "Reporting Security Issues", - "path": "/v1.12-docs/contributing/security.md" - }, - { - "title": "Coding Conventions", - "path": "/v1.12-docs/contributing/coding-conventions.md" - }, - { - "title": "Third Party Code Donations", - "path": "/v1.12-docs/contributing/third-party-code-donation.md" - }, - { - "title": "Signing Keys", - "path": "/v1.12-docs/contributing/signing-keys.md" - }, - { - "title": "Importing cert-manager in Go", - "path": "/v1.12-docs/contributing/importing.md" - } - ] - }, { "title": "Concepts", "routes": [ diff --git a/content/v1.12-docs/projects/README.md b/content/v1.12-docs/projects/README.md deleted file mode 100644 index 185df2fce4..0000000000 --- a/content/v1.12-docs/projects/README.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Projects -description: 'Satellite Projects of cert-manager' ---- - -The cert-manager project has a number of [satellite projects](https://github.com/cert-manager) -that extend the project's functionality, and complement the core cert-manager feature-set. - -These tools help with security, compliance and control. - -- [istio-csr](./istio-csr.md): Secure Istio service mesh with istio-csr which is - an agent that allows for [Istio](https://istio.io) workload and control plane - components to be secured using cert-manager. -- [approver-policy](./approver-policy/README.md): - a cert-manager **approver** that will automatically approve or deny - certificate requests based on defined policy. -- [csi-driver](./csi-driver.md): - a Container Storage Interface (CSI) driver plugin for Kubernetes to work along - cert-manager. The goal for this plugin is to seamlessly request and mount - certificate key pairs to pods. This is useful for facilitating mTLS, or - otherwise securing connections of pods with guaranteed present certificates - whilst having all of the features that cert-manager provides. -- [csi-driver-spiffe](./csi-driver-spiffe.md): - another CSI driver plugin to work along cert-manager. This CSI driver - transparently delivers [SPIFFE](https://spiffe.io/) - [SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-verifiable-identity-document-svid) - in the form of X.509 certificate key pairs to mounting Kubernetes Pods. The - end result is all and any Pod running in Kubernetes can securely request their - SPIFFE identity document from a Trust Domain with minimal configuration. -- [trust-manager](./trust-manager/README.md): the easiest way to manage TLS trust bundles in Kubernetes and OpenShift clusters. -- [trust-manager API reference](./trust-manager/api-reference.md): full documentation of the trust-manager CRD(s) diff --git a/content/v1.12-docs/projects/approver-policy/README.md b/content/v1.12-docs/projects/approver-policy/README.md deleted file mode 100644 index fa0c9d6b87..0000000000 --- a/content/v1.12-docs/projects/approver-policy/README.md +++ /dev/null @@ -1,422 +0,0 @@ ---- -title: approver-policy -description: 'Policy plugin for cert-manager' ---- - -approver-policy is a cert-manager -[approver](../../concepts/certificaterequest.md#approval) -that will approve or deny CertificateRequests based on policies defined in -the `CertificateRequestPolicy` custom resource. - -## Prerequisites - -[cert-manager must be installed](../../installation/README.md), and -the [the default approver in cert-manager must be disabled](../../concepts/certificaterequest.md#approver-controller). - -> ⚠️ If the default approver is not disabled in cert-manager, approver-policy will -> race with cert-manager and policy will be ineffective. - -If you install cert-manager using `helm install` or `helm upgrade`, -you can disable the default approver by [Customizing the Chart Before Installing](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing) using the `--set` or `--values` command line flags: - -``` -# Example --set value ---set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver -``` - -```yaml -# Example --values file content -extraArgs: - - "--controllers=*,-certificaterequests-approver" # ⚠ Disable cert-manager's built-in approver -``` - -Here's a full example which will install cert-manager or reconfigure it if it is already installed: - -```terminal -helm upgrade cert-manager jetstack/cert-manager \ - --install \ - --create-namespace \ - --namespace cert-manager \ - --version REPLACE-WITH-YOUR-CERT-MANAGER-VERSION \ - --set installCRDs=true \ - --set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver -``` - -> ℹ️ The `--set installCRDs=true` setting is a convenient way to install the -> cert-manager CRDS, but it is optional and has some drawbacks. -> Read [Helm: Installing Custom Resource Definitions](https://deploy-preview-1216--cert-manager-website.netlify.app/docs/installation/helm/#3-install-customresourcedefinitions) to learn more. -> -> ℹ️ Be sure to customize the cert-manager controller `extraArgs`, -> which are at the top level of the values file. -> *Do not* change the `webhook.extraArgs`, `startupAPICheck.extraArgs` or `cainjector.extraArgs` settings. -> -> ⚠️ If you are reconfiguring an already installed cert-manager, -> check whether the original installation already customized the `extraArgs` value -> by running `helm get values cert-manager --namespace cert-manager`. -> If there are already `extraArgs` values, merge those with the extra `--controllers` value. -> Otherwise your original `extraArgs` values will be overwritten. - -## Installation - -To install approver-policy: - -```terminal -helm repo add jetstack https://charts.jetstack.io --force-update -helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait -``` - -If you are using approver-policy with [external -issuers](../../configuration/external.md), you _must_ -include their signer names so that approver-policy has permissions to approve -and deny CertificateRequests that -[reference them](../../concepts/certificaterequest.md#rbac-syntax). -For example, if using approver-policy for the internal issuer types, along with -[google-cas-issuer](https://github.com/jetstack/google-cas-issuer), and -[aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer), -set the following values when installing: - -```terminal -$ helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait \ - --set app.approveSignerNames="{\ -issuers.cert-manager.io/*,clusterissuers.cert-manager.io/*,\ -googlecasclusterissuers.cas-issuer.jetstack.io/*,googlecasissuers.cas-issuer.jetstack.io/*,\ -awspcaclusterissuers.awspca.cert-manager.io/*,awspcaissuers.awspca.cert-manager.io/*\ -}" -``` - -## Configuration - -> Example policy resources can be found -> [here](https://github.com/cert-manager/approver-policy/tree/main/docs/examples). - -When a CertificateRequest is created, approver-policy will evaluate whether the -request is appropriate for any existing policy, and if so, evaluate whether it -should be approved or denied. - -For a CertificateRequest to be appropriate for a policy and therefore be -evaluated by it, it must be both bound via RBAC _and_ be selected by the policy -selector. CertificateRequestPolicy currently supports `issuerRef` and `namespace` -as a selector. - -**If at least one policy permits the request, the request is approved. If at -least one policy is appropriate for the request but none of those permit the -request, the request is denied.** - -A denied CertificateRequest is considered to be permanently failed. If it was -created for a Certificate resource, the issuance will be retried with -[exponential backoff](../../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) -like all other permanent issuance failures. A CertificateRequest that is neither -approved nor denied (because no matching policy was found) will not be further -processed by cert-manager until it gets either approved or denied. - -CertificateRequestPolicies are cluster scoped resources that can be thought of -as "policy profiles". They describe any request that is approved by that -policy. Policies are bound to Kubernetes users and ServiceAccounts using RBAC. - -Below is an example of a policy that is bound to all Kubernetes users who may -only request certificates that have the common name of `"hello.world"`. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: test-policy -spec: - allowed: - commonName: - value: "hello.world" - required: true - selector: - # Select all IssuerRef - issuerRef: {} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: cert-manager-policy:hello-world -rules: - - apiGroups: ["policy.cert-manager.io"] - resources: ["certificaterequestpolicies"] - verbs: ["use"] - # Name of the CertificateRequestPolicies to be used. - resourceNames: ["test-policy"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: cert-manager-policy:hello-world -roleRef: -# ClusterRole or Role _must_ be bound to a user for the policy to be considered. - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cert-manager-policy:hello-world -subjects: -# The users who should be bound to the policies defined. -# Note that in the case of users creating Certificate resources, cert-manager -# is the entity that is creating the actual CertificateRequests, and so the -# cert-manager controller's -# Service Account should be bound instead. -- kind: Group - name: system:authenticated - apiGroup: rbac.authorization.k8s.io -``` - -## Behavior - -CertificateRequestPolicy are split into 4 parts; `allowed`, `contraints`, -`selector`, and `plugins`. - -### Allowed - -Allowed is the block that defines attributes that match against the -corresponding attribute in the request. A request is permitted by the policy if -the request omits an allowed attribute, but will _deny_ the request if it -contains an attribute which is _not_ present in the allowed block. - -An allowed attribute can be marked as `required`, which if true, will enforce -that the attribute has been defined in the request. A field can only be marked -as `required` if the corresponding field is also defined. The `required` field -is not available for `isCA` or `usages`. - -In the following CertificateRequestPolicy, a request will be permitted if it -does not request a DNS name, requests the DNS name `"example.com"`, but will be -denied when requesting `"bar.example.com"`. - -```yaml -spec: - ... - allowed: - dnsNames: - values: - - "example.com" - - "foo.example.com" - ... -``` - -In the following, a request will be denied if the request contains no Common -Name, but will permit requests whose Common Name ends in ".com". - -```yaml -spec: - ... - allowed: - commonName: - value: "*.com" - required: true - ... -``` - -If an allowed field is omitted, that attribute is considered "deny all" for -requests. - -Allowed string fields accept wildcards "\*" within its values. Wildcards "\*" in -patterns represent any string that has a length of 0 or more. A pattern -containing only "\*" will match anything. A pattern containing `"\*foo"` will -match `"foo"` as well as any string which ends in `"foo"` (e.g. `"bar-foo"`). A -pattern containing `"\*.foo"` will match `"bar-123.foo"`, but not `"barfoo"`. - -Allowed fields that are lists will permit requests that are a subset of that -list. This means that if `usages` contains `["server auth", "client auth"]`, -then a request containing only `["server auth"]` would be permitted, but not -`["server auth", "cert sign"]`. - -Below is an example including all supported allowed fields of -CertificateRequestPolicy. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: my-policy -spec: - allowed: - commonName: - value: "example.com" - dnsNames: - values: - - "example.com" - - "*.example.com" - ipAddresses: - values: - - "1.2.3.4" - - "10.0.1.*" - uris: - values: - - "spiffe://example.org/ns/*/sa/*" - emailAddresses: - values: - - "*@example.com" - required: true - isCA: false - usages: - - "server auth" - - "client auth" - subject: - organizations: - values: ["hello-world"] - countries: - values: ["*"] - organizationalUnits: - values: ["*"] - localities: - values: ["*"] - provinces: - values: ["*"] - streetAddresses: - values: ["*"] - postalCodes: - values: ["*"] - serialNumber: - value: "*" - ... -``` - -### Constraints - -Constraints is the block that is used to limit what attributes the request can -have. If a constraint is not defined, then the attribute is considered "allow -all". - -Below is an example containing all supported constraints fields of -CertificateRequestPolicy. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: my-policy -spec: - ... - constraints: - minDuration: 1h - maxDuration: 24h - privateKey: - algorithm: RSA - minSize: 2048 - maxSize: 4096 - ... -``` - -### Selector - -Selector is a required field that is used for matching -CertificateRequestPolicies against CertificateRequests for evaluation. A -CertificateRequestPolicy must select, and therefore match, a CertificateRequest -for it to be considered for evaluation of the request. - -> ⚠️ Note that the user must still be bound by [RBAC](#configuration) for -> the policy to be considered for evaluation against a request. - -approver-policy supports selecting over the `issuerRef` and the `namespace` of a -request. - -At least either an `issuerRef` *or* `namespace` selector must be defined, even -if set to empty (`{}`). **Both** selectors must match on a CertificateRequest -for the request to evaluated by the policy if both are defined. - -#### `issuerRef` - -The `issuerRef` CertificateRequestPolicy selector selects on the corresponding -`issuerRef` stanza on the CertificateRequest. - -`issuerRef` values accept wildcards "\*". If an `issuerRef` is set to an empty -object `{}`, then the policy will match against _all_ requests. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: my-policy -spec: - ... - selector: - issuerRef: - name: "my-ca" - kind: "*Issuer" - group: "cert-manager.io" -``` - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: match-all-requests -spec: - ... - selector: - issuerRef: {} -``` - -#### `namespace` - -The `namespace` CertificateRequestPolicy selector selects on the Namespace to -which the CertificateRequest was created in. The selector can be defined with -either `matchNames` or `matchLabels`. - -`matchNames` takes a list of strings which match the _name_ of the Namespace. -Accepts wildcards "\*". - -`matchLabels` takes a list of key value strings which match on the labels of the -Namespace that the CertificateRequest was created in. Please see the [Kubernetes -documentation][] for more information on `matchLabels` behavior. - -If a `namespace` is set to an empty object `{}`, then the policy will match -against _all_ requests. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: my-policy -spec: - ... - selector: - namespace: - matchNames: - - "default" - - "app-team-*" - matchLabels: - foo: bar - team: dev -``` - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: match-all-requests -spec: - ... - selector: - namespace: {} -``` - -[Kubernetes documentation]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#resources-that-support-set-based-requirements - -### Plugins - -Plugins are external approvers that are built into approver-policy at compile -time. Plugins are designed to be used as extensions to the existing policy -checks where the user requires special functionality that the existing checks -can't provide. - -Plugins are defined as a block on the CertificateRequestPolicy `spec`. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: plugins -spec: - ... - plugins: - my-plugin: - values: - val-1: key-1 -``` - -There are currently no open source plugins. - -## API Reference - -> 📖 Read the [approver-policy API reference](api-reference.md). diff --git a/content/v1.12-docs/projects/approver-policy/api-reference.md b/content/v1.12-docs/projects/approver-policy/api-reference.md deleted file mode 100644 index e4d45bb829..0000000000 --- a/content/v1.12-docs/projects/approver-policy/api-reference.md +++ /dev/null @@ -1,978 +0,0 @@ ---- -title: approver-policy API Reference -description: "approver-policy API documentation" ---- - -Packages: - -- [`policy.cert-manager.io/v1alpha1`](#policycert-manageriov1alpha1) - -# `policy.cert-manager.io/v1alpha1` - -Resource Types: - - -- [CertificateRequestPolicy](#certificaterequestpolicy) - - - - -## `CertificateRequestPolicy` - - - - - -CertificateRequestPolicy is an object for describing a "policy profile" that makes decisions on whether applicable CertificateRequests should be approved or denied. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              apiVersionstringpolicy.cert-manager.io/v1alpha1true
              kindstringCertificateRequestPolicytrue
              metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
              specobject - CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy.
              -
              false
              statusobject - CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy.
              -
              false
              - - -### `CertificateRequestPolicy.spec` - - -CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              selectorobject - Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation.
              -
              true
              allowedobject - Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible.
              -
              false
              constraintsobject - Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute.
              -
              false
              pluginsmap[string]object - Plugins define a set of plugins and their configuration that should be executed when this policy is evaluated against a CertificateRequest. A plugin must already be built within approver-policy for it to be available.
              -
              false
              - - -### `CertificateRequestPolicy.spec.selector` - - -Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              issuerRefobject - IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". - The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ```
              -
              false
              namespaceobject - Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected.
              -
              false
              - - -### `CertificateRequestPolicy.spec.selector.issuerRef` - - -IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". - The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ``` - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              groupstring - Group is the wildcard selector to match the `spec.issuerRef.group` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
              -
              false
              kindstring - Kind is the wildcard selector to match the `spec.issuerRef.kind` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
              -
              false
              namestring - Name is the wildcard selector to match the `spec.issuerRef.name` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
              -
              false
              - - -### `CertificateRequestPolicy.spec.selector.namespace` - - -Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              matchLabelsmap[string]string - MatchLabels is the set of Namespace labels that select on CertificateRequests which have been created in a Namespace matching the selector.
              -
              false
              matchNames[]string - MatchNames are the set of Namespace names that select on CertificateRequests that have been created in a matching Namespace. Accepts wildcards "*".
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed` - - -Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              commonNameobject - CommonName defines the X.509 Common Name that is permissible.
              -
              false
              dnsNamesobject - DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*".
              -
              false
              emailAddressesobject - EmailAddresses defines the X.509 Email SANs that may be requested for.
              -
              false
              ipAddressesobject - IPAddresses defines the X.509 IP SANs that may be requested for.
              -
              false
              isCAboolean - IsCA defines whether it is permissible for a CertificateRequest to have the `spec.IsCA` field set to `true`. An omitted field, value of `nil` or `false`, forbids the `spec.IsCA` field from bring `true`. A value of `true` permits CertificateRequests setting the `spec.IsCA` field to `true`.
              -
              false
              subjectobject - Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested.
              -
              false
              urisobject - URIs defines the X.509 URI SANs that may be requested for.
              -
              false
              usages[]enum - Usages defines the list of permissible key usages that may appear on the CertificateRequest `spec.keyUsages` field. An omitted field or value of `nil` forbids any Usages being requested. An empty slice `[]` is equivalent to `nil`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.commonName` - - -CommonName defines the X.509 Common Name that is permissible. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
              -
              false
              valuestring - Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.dnsNames` - - -DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*". - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.emailAddresses` - - -EmailAddresses defines the X.509 Email SANs that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.ipAddresses` - - -IPAddresses defines the X.509 IP SANs that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject` - - -Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              countriesobject - Countries define the X.509 Subject Countries that may be requested for.
              -
              false
              localitiesobject - Localities defines the X.509 Subject Localities that may be requested for.
              -
              false
              organizationalUnitsobject - OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for.
              -
              false
              organizationsobject - Organizations define the X.509 Subject Organizations that may be requested for.
              -
              false
              postalCodesobject - PostalCodes defines the X.509 Subject Postal Codes that may be requested for.
              -
              false
              provincesobject - Provinces defines the X.509 Subject Provinces that may be requested for.
              -
              false
              serialNumberobject - SerialNumber defines the X.509 Subject Serial Number that may be requested for.
              -
              false
              streetAddressesobject - StreetAddresses defines the X.509 Subject Street Addresses that may be requested for.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.countries` - - -Countries define the X.509 Subject Countries that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.localities` - - -Localities defines the X.509 Subject Localities that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.organizationalUnits` - - -OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.organizations` - - -Organizations define the X.509 Subject Organizations that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.postalCodes` - - -PostalCodes defines the X.509 Subject Postal Codes that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.provinces` - - -Provinces defines the X.509 Subject Provinces that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.serialNumber` - - -SerialNumber defines the X.509 Subject Serial Number that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
              -
              false
              valuestring - Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.streetAddresses` - - -StreetAddresses defines the X.509 Subject Street Addresses that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.uris` - - -URIs defines the X.509 URI SANs that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.constraints` - - -Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute. - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              maxDurationstring - MaxDuration defines the maximum duration a certificate may be requested for. Values are inclusive (i.e. a max value of `1h` will accept a duration of `1h`). MaxDuration and MinDuration may be the same value. An omitted field or value of `nil` permits any maximum duration. If MaxDuration is defined, a duration _must_ be requested on the CertificateRequest.
              -
              false
              minDurationstring - MinDuration defines the minimum duration a certificate may be requested for. Values are inclusive (i.e. a min value of `1h` will accept a duration of `1h`). MinDuration and MaxDuration may be the same value. An omitted field or value of `nil` permits any minimum duration. If MinDuration is defined, a duration _must_ be requested on the CertificateRequest.
              -
              false
              privateKeyobject - PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor.
              -
              false
              - - -### `CertificateRequestPolicy.spec.constraints.privateKey` - - -PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor. - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              algorithmenum - Algorithm defines the allowed crypto algorithm that is used by the requestor for their private key in their request. An omitted field or value of `nil` permits any Algorithm.
              -
              - Enum: RSA, ECDSA, Ed25519
              -
              false
              maxSizeinteger - MaxSize defines the maximum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MaxSize and MinSize may be the same value. An omitted field or value of `nil` permits any maximum size.
              -
              false
              minSizeinteger - MinSize defines the minimum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MinSize and MaxSize may be the same value. An omitted field or value of `nil` permits any minimum size.
              -
              false
              - - -### `CertificateRequestPolicy.spec.plugins[key]` - - -CertificateRequestPolicyPluginData is configuration needed by the plugin approver to evaluate a CertificateRequest on this policy. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              valuesmap[string]string - Values define a set of well-known, to the plugin, key value pairs that are required for the plugin to successfully evaluate a request based on this policy.
              -
              false
              - - -### `CertificateRequestPolicy.status` - - -CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              conditions[]object - List of status conditions to indicate the status of the CertificateRequestPolicy. Known condition types are `Ready`.
              -
              false
              - - -### `CertificateRequestPolicy.status.conditions[index]` - - -CertificateRequestPolicyCondition contains condition information for a CertificateRequestPolicyStatus. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              statusstring - Status of the condition, one of ('True', 'False', 'Unknown').
              -
              true
              typestring - Type of the condition, known values are (`Ready`).
              -
              true
              lastTransitionTimestring - LastTransitionTime is the timestamp corresponding to the last status change of this condition.
              -
              - Format: date-time
              -
              false
              messagestring - Message is a human readable description of the details of the last transition, complementing reason.
              -
              false
              observedGenerationinteger - If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the CertificateRequestPolicy.
              -
              - Format: int64
              -
              false
              reasonstring - Reason is a brief machine readable explanation for the condition's last transition.
              -
              false
              diff --git a/content/v1.12-docs/projects/csi-driver-spiffe.md b/content/v1.12-docs/projects/csi-driver-spiffe.md deleted file mode 100644 index 771ce6a1ae..0000000000 --- a/content/v1.12-docs/projects/csi-driver-spiffe.md +++ /dev/null @@ -1,257 +0,0 @@ ---- -title: csi-driver-spiffe -description: 'Container Storage Interface (CSI) driver plugin for Kubernetes, providing SPIFFE SVIDs using cert-manager' ---- - -csi-driver-spiffe is a Container Storage Interface (CSI) driver plugin for -Kubernetes, designed to work alongside [cert-manager](https://cert-manager.io/). - -It transparently delivers [SPIFFE](https://spiffe.io/) [SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-verifiable-identity-document-svid) -(in the form of X.509 certificate key pairs) to mounting Kubernetes Pods. - -The end result is that any and all Pods running in Kubernetes can securely request -a SPIFFE identity document from a Trust Domain with minimal configuration. - -These documents in turn have the following properties: - -- automatically renewed ✔️ -- private key never leaves the node's virtual memory ✔️ -- each Pod's document is unique ✔️ -- the document shares the same life cycle as the Pod and is destroyed on Pod termination ✔️ - -```yaml -... - volumeMounts: - - mountPath: "/var/run/secrets/spiffe.io" - name: spiffe - volumes: - - name: spiffe - csi: - driver: spiffe.csi.cert-manager.io - readOnly: true -``` - -SPIFFE documents can then be used by Pods for mutual TLS (mTLS) or other authentication within their Trust Domain. -### Components - -The project is split into two components. - -#### CSI Driver - -The CSI driver runs as DaemonSet on the cluster which is responsible for -generating, requesting, and mounting the certificate key pair to Pods on the -node it manages. The CSI driver creates and manages a -[tmpfs](https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html) directory -which is used to create and mount Pod volumes from. - -When a Pod is created with the CSI volume configured, the -driver will locally generate a private key, and create a cert-manager -[CertificateRequest](../concepts/certificaterequest.md) -in the same Namespace as the Pod. - -The driver uses [CSI Token Request](https://kubernetes-csi.github.io/docs/token-requests.html) to both -discover the Pod's identity to form the SPIFFE identity contained in the X.509 -certificate signing request, as well as securely impersonate its ServiceAccount -when creating the CertificateRequest. - -Once signed by the pre-configured target signer, the driver will mount the -private key and signed certificate into the Pod's Volume to be made available as -a Volume Mount. This certificate key pair is regularly renewed based on the -expiry of the signed certificate. - -#### Approver - -A distinct [cert-manager approver](../concepts/certificaterequest.md#approval) -Deployment is responsible for managing the approval and denial condition of -created CertificateRequests that target the configured SPIFFE Trust Domain -signer. - -The approver ensures that requests have: - -1. acceptable key usages (Key Encipherment, Digital Signature, Client Auth, Server Auth); -2. a requested duration which matches the enforced duration (default 1 hour); -3. no [SANs](https://en.wikipedia.org/wiki/Subject_Alternative_Name) or other - identifiable attributes except a single [URI SAN](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier); -4. a URI SAN which is the SPIFFE identity of the ServiceAccount which created - the CertificateRequest; -5. a SPIFFE ID Trust Domain matching the one that was configured at startup. - -If any of these checks do not pass, the CertificateRequest will be marked as -Denied, else it will be marked as Approved. The approver will only manage -CertificateRequests who request from the same [IssuerRef](../concepts/certificaterequest.md) -that has been configured. - -## Installation - -### Requirements - -csi-driver-spiffe generally requires Kubernetes version `v1.21` or newer. - -If running on Kubernetes `v1.20`, you'll need the `--feature-gates=CSIServiceAccountToken=true` flag. - -cert-manager `v1.3` or higher is also required. - -### Steps - -#### 1. Install cert-manager - -csi-driver-spiffe requires cert-manager to be [installed](../installation/README.md) but -a default installation of cert-manager **will not work**. - -> ⚠️ It is **vital** that the [default approver is disabled in cert-manager](../concepts/certificaterequest.md#approver-controller) ⚠️ - -If the default approver is not disabled, the csi-driver-spiffe approver will -race with cert-manager and policy enforcement will become useless. - -```bash -helm repo add jetstack https://charts.jetstack.io --force-update - -# NOTE: This isn't the usual cert-manager install process; -# we're disabling the cert-manager approver. -# See explanation above! - -helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager \ - --set extraArgs={--controllers='*\,-certificaterequests-approver'} \ - --set installCRDs=true \ - --create-namespace -``` - -#### 2. Configure an Issuer / ClusterIssuer - -Install or configure a [ClusterIssuer](../configuration/README.md) to give -cert-manager the ability to sign against your Trust Domain. - -If you want a namespace-scoped Issuer, then it must be created in every namespace -that Pods will mount volumes from. - -You must use an Issuer type which is compatible with signing URI SAN certificates; -ACME issuers won't generally work, and the SelfSigned issuer is not appropriate. - -An example demo [ClusterIssuer](../concepts/issuer.md#namespaces) can -be found [in the csi-driver-spiffe repo](https://github.com/cert-manager/csi-driver-spiffe/blob/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/clusterissuer.yaml). - -> ⚠️ This Trust Domain's root CA is generated by cert-manager and **the private key is stored in the cluster** -> This might not be appropriate for production deployments! - -We'll also use [cmctl](../reference/cmctl.md) to approve the CertificateRequest, -since the default approver was disabled above. - -```terminal -kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/clusterissuer.yaml - -# We must also approve the CertificateRequest since we -# disabled the default approver -cmctl approve -n cert-manager \ - $(kubectl get cr -n cert-manager -ojsonpath='{.items[0].metadata.name}') -``` - -#### 3. Install csi-driver-spiffe - -Install csi-driver-spiffe into the cluster using the issuer we configured. We -must also configure the issuer resource type and name of the issuer we -configured so that the approver has [permissions to approve referencing CertificateRequests](../concepts/certificaterequest.md#rbac-syntax). - -Note that the `issuer.name`, `issuer.kind` and `issuer.group` will need to be changed to match -the issuer you're actually using! - -```bash -helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \ - --set "app.logLevel=1" \ - --set "app.trustDomain=my.trust.domain" \ - --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \ - \ - --set "app.issuer.name=csi-driver-spiffe-ca" \ - --set "app.issuer.kind=ClusterIssuer" \ - --set "app.issuer.group=cert-manager.io" -``` - -## Usage - -Once the driver is successfully installed, Pods can begin to request and mount -their key and SPIFFE certificate. Since the Pod's ServiceAccount is impersonated -when creating CertificateRequests, every ServiceAccount must be given that -permission which intends to use the volume. - -Example manifest with a dummy Deployment: - -```bash -kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/example-app.yaml - -kubectl exec -n sandbox \ - $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') \ - -- \ - cat /var/run/secrets/spiffe.io/tls.crt | \ - openssl x509 --noout --text | \ - grep "Issuer:" -# expected output: Issuer: CN = csi-driver-spiffe-ca - -kubectl exec -n sandbox \ - $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') \ - -- \ - cat /var/run/secrets/spiffe.io/tls.crt | \ - openssl x509 --noout --text | \ - grep "URI:" -# expected output: URI:spiffe://foo.bar/ns/sandbox/sa/example-app -``` - -### FS-Group - -When running Pods with a specified user or group, the volume will not be -readable by default due to Unix based file system permissions. The mounting -volumes file group can be specified using the following volume attribute: - -```yaml -... - securityContext: - runAsUser: 123 - runAsGroup: 456 - volumes: - - name: spiffe - csi: - driver: spiffe.csi.cert-manager.io - readOnly: true - volumeAttributes: - spiffe.csi.cert-manager.io/fs-group: "456" -``` - -```bash -kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/fs-group-app.yaml - -kubectl exec -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app-fs-group -o jsonpath='{.items[0].metadata.name}') -- cat /var/run/secrets/spiffe.io/tls.crt | openssl x509 --noout --text | grep URI: -# expected output: URI:spiffe://foo.bar/ns/sandbox/sa/fs-group-app -``` - -### Root CA Bundle - -By default, the CSI driver will only mount the Pod's private key and signed -certificate. csi-driver-spiffe can be optionally configured to also mount a -statically defined CA bundle from a volume that will be written to all Pod -volumes. - -If the CSI driver detects this bundle has changed (through overwrite, renewal, -etc), the new bundle will be written to all existing volumes. - -The following example mounts the CA certificate used by the Trust Domain -ClusterIssuer. - -```terminal -helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \ - --set "app.logLevel=1" \ - --set "app.trustDomain=my.trust.domain" \ - --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \ - \ - --set "app.issuer.name=csi-driver-spiffe-ca" \ - --set "app.issuer.kind=ClusterIssuer" \ - --set "app.issuer.group=cert-manager.io" \ - \ - --set "app.driver.volumes[0].name=root-cas" \ - --set "app.driver.volumes[0].secret.secretName=csi-driver-spiffe-ca" \ - --set "app.driver.volumeMounts[0].name=root-cas" \ - --set "app.driver.volumeMounts[0].mountPath=/var/run/secrets/cert-manager-csi-driver-spiffe" \ - --set "app.driver.sourceCABundle=/var/run/secrets/cert-manager-csi-driver-spiffe/ca.crt" - -kubectl rollout restart deployment -n sandbox my-csi-app - -kubectl exec -it -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') -- ls /var/run/secrets/spiffe.io/ -# expected output: ca.crt tls.crt tls.key -``` diff --git a/content/v1.12-docs/projects/csi-driver.md b/content/v1.12-docs/projects/csi-driver.md deleted file mode 100644 index 31f7f1c74d..0000000000 --- a/content/v1.12-docs/projects/csi-driver.md +++ /dev/null @@ -1,231 +0,0 @@ ---- -title: csi-driver -description: '' ---- - -csi-driver is a Container Storage Interface (CSI) driver plugin for Kubernetes -to work along cert-manager. The goal for this plugin is to seamlessly request -and mount certificate key pairs to pods. This is useful for facilitating mTLS, -or otherwise securing connections of pods with guaranteed present certificates -whilst having all of the features that cert-manager provides. - -## Why a CSI Driver? - -- Ensure private keys never leave the node and are never sent over the network. - All private keys are stored locally on the node. -- Unique key and certificate per application replica with a grantee to be - present on application run time. -- Reduce resource management overhead by defining certificate request spec - in-line of the Kubernetes Pod template. -- Automatic renewal of certificates based on expiry of each individual - certificate. -- Keys and certificates are destroyed during application termination. -- Scope for extending plugin behavior with visibility on each replica's - certificate request and termination. - -## Requirements and Installation - -This CSI driver plugin makes use of the 'CSI inline volume' feature - Alpha as -of `v1.15` and beta in `v1.16`. Kubernetes versions `v1.16` and higher require -no extra configuration however `v1.15` requires the following feature gate set: -``` ---feature-gates=CSIInlineVolume=true -``` - -You must have a working installation of cert-manager present on the cluster. -Instructions on how to install cert-manager can be found -[on cert-manager.io](../installation/README.md). - -To install the csi-driver, use helm install: - -```terminal -helm repo add jetstack https://charts.jetstack.io --force-update -helm upgrade -i -n cert-manager cert-manager-csi-driver jetstack/cert-manager-csi-driver --wait -``` - -Or apply the static manifests to your cluster: - -```terminal -helm repo add jetstack https://charts.jetstack.io --force-update -helm template jetstack/cert-manager-csi-driver | kubectl apply -n cert-manager -f - -``` - - -You can verify the installation has completed correctly by checking the presence -of the CSIDriver resource as well as a CSINode resource present for each node, -referencing `csi.cert-manager.io`. - -``` -$ kubectl get csidrivers -NAME CREATED AT -csi.cert-manager.io 2019-09-06T16:55:19Z - -$ kubectl get csinodes -o yaml -apiVersion: v1 -items: -- apiVersion: storage.k8s.io/v1beta1 - kind: CSINode - metadata: - name: kind-control-plane - ownerReferences: - - apiVersion: v1 - kind: Node - name: kind-control-plane -... - spec: - drivers: - - name: csi.cert-manager.io - nodeID: kind-control-plane - topologyKeys: null -... -``` - -The CSI driver is now installed and is ready to be used for pods in the cluster. - -## Requesting and Mounting Certificates - -To request certificates from cert-manager, simply define a volume mount where -the key and certificate will be written to, along with a volume with attributes -that define the cert-manager request. The following is a dummy app that mounts a -key certificate pair to `/tls` and has been signed by the `ca-issuer` with a DNS -name valid for `my-service.sandbox.svc.cluster.local`. - -``` -apiVersion: v1 -kind: Pod -metadata: - name: my-csi-app - namespace: sandbox - labels: - app: my-csi-app -spec: - containers: - - name: my-frontend - image: busybox - volumeMounts: - - mountPath: "/tls" - name: tls - command: [ "sleep", "1000000" ] - volumes: - - name: tls - csi: - driver: csi.cert-manager.io - volumeAttributes: - csi.cert-manager.io/issuer-name: ca-issuer - csi.cert-manager.io/dns-names: ${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local -``` - -Once created, the CSI driver will generate a private key locally, request a -certificate from cert-manager based on the given attributes, then store both -locally to be mounted to the pod. The pod will remain in a pending state until -this process has been completed. - -For more information on how to set up issuers for your cluster, refer to the -cert-manager documentation -[here](../configuration/README.md). **Note** it is not -possible to use `SelfSigned` Issuers with the CSI Driver. In order for -cert-manager to self sign a certificate, it needs access to the secret -containing the private key that signed the certificate request to sign the end -certificate. This secret is not used and so not available in the CSI driver use -case. - -## Supported Volume Attributes - -The csi-driver driver aims to have complete feature parity with all possible -values available through the cert-manager API however currently supports the -following values; - -| Attribute | Description | Default | Example | -|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|----------------------------------| -| `csi.cert-manager.io/issuer-name` | The Issuer name to sign the certificate request. | | `ca-issuer` | -| `csi.cert-manager.io/issuer-kind` | The Issuer kind to sign the certificate request. | `Issuer` | `ClusterIssuer` | -| `csi.cert-manager.io/issuer-group` | The group name the Issuer belongs to. | `cert-manager.io` | `out.of.tree.foo` | -| `csi.cert-manager.io/common-name` | Certificate common name (supports variables). | | `my-cert.foo` | -| `csi.cert-manager.io/dns-names` | DNS names the certificate will be requested for. At least a DNS Name, IP or URI name must be present (supports variables). | | `a.b.foo.com,c.d.foo.com` | -| `csi.cert-manager.io/ip-sans` | IP addresses the certificate will be requested for. | | `192.0.0.1,192.0.0.2` | -| `csi.cert-manager.io/uri-sans` | URI names the certificate will be requested for (supports variables). | | `spiffe://foo.bar.cluster.local` | -| `csi.cert-manager.io/duration` | Requested duration the signed certificate will be valid for. | `720h` | `1880h` | -| `csi.cert-manager.io/is-ca` | Mark the certificate as a certificate authority. | `false` | `true` | -| `csi.cert-manager.io/key-usages` | Set the key usages on the certificate request. | `digital signature,key encipherment` | `server auth,client auth` | -| `csi.cert-manager.io/key-encoding` | Set the key encoding format (PKCS1 or PKCS8). | `PKCS1` | `PKCS8` | -| `csi.cert-manager.io/certificate-file` | File name to store the certificate file at. | `tls.crt` | `foo.crt` | -| `csi.cert-manager.io/ca-file` | File name to store the ca certificate file at. | `ca.crt` | `foo.ca` | -| `csi.cert-manager.io/privatekey-file` | File name to store the key file at. | `tls.key` | `foo.key` | -| `csi.cert-manager.io/fs-group` | Set the FS Group of written files. Should be paired with and match the value of the consuming container `runAsGroup`. | | `2000` | -| `csi.cert-manager.io/renew-before` | The time to renew the certificate before expiry. Defaults to a third of the requested duration. | `$CERT_DURATION/3` | `72h` | -| `csi.cert-manager.io/reuse-private-key` | Re-use the same private when when renewing certificates. | `false` | `true` | -| `csi.cert-manager.io/pkcs12-enable` | Enable writing the signed certificate chain and private key as a PKCS12 file. | | `true` | -| `csi.cert-manager.io/pkcs12-filename` | File location to write the PKCS12 file. Requires `csi.cert-manager.io/keystore-pkcs12-enable` be set to `true`. | `keystore.p12` | `tls.p12` | -| `csi.cert-manager.io/pkcs12-password` | Password used to encode the PKCS12 file. Required when PKCS12 is enabled (`csi.cert-manager.io/keystore-pkcs12-enable: true`). | | `my-password` | - -### Variables - -The following attributes support variables that are evaluated when a request is -made for the mounting Pod. These variables are useful for constructing requests -with SANs that contain values from the mounting Pod. - -``` -`csi.cert-manager.io/common-name` -`csi.cert-manager.io/dns-names` -`csi.cert-manager.io/uri-sans` -``` - -Variables follow the [go `os.Expand`](https://pkg.go.dev/os#Expand) structure, -which is generally what you would expect on a UNIX shell. The CSI driver has -access to the following variables: - -``` -${POD_NAME} -${POD_NAMESPACE} -${POD_UID} -${SERVICE_ACCOUNT_NAME} -``` - -#### Example Usage - -```yaml -volumeAttributes: - csi.cert-manager.io/issuer-name: ca-issuer - csi.cert-manager.io/dns-names: "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local" - csi.cert-manager.io/uri-sans: "spiffe://cluster.local/ns/${POD_NAMESPACE}/pod/${POD_NAME}/${POD_UID}" - csi.cert-manager.io/common-name: "${SERVICE_ACCOUNT_NAME}.${POD_NAMESPACE}" -``` - -## Requesting Certificates using the mounting Pod's ServiceAccount - -If the flag `--use-token-request` is enabled on the csi-driver DaemonSet, the -[CertificateRequest](../concepts/certificaterequest.md) resource will be created -by the mounting Pod's ServiceAccount. This can be pared with -[approver-policy](./approver-policy/README.md) to enable advanced policy on a per -ServiceAccount basis. - -Ensure to give permissions to Pod ServiceAccounts to create CertificateRequests -with this flag enabled, i.e: - -```yaml -# WARNING: This RBAC will enable any identiy in the cluster to create -# CertificateRequests. This may or may not be problimatic based on your security -# model. It is likely worth scoping the set of identities in the -# `ClusterRoleBinding` `subjects` stanza. -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: cert-manager-csi-driver-all-cr-create -rules: -- apiGroups: ["cert-manager.io"] - resources: ["certificaterequests"] - verbs: [ "create" ] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: cert-manager-csi-driver-all-cr-create -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cert-manager-csi-driver-all-cr-create -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: system:authenticated -``` diff --git a/content/v1.12-docs/projects/istio-csr.md b/content/v1.12-docs/projects/istio-csr.md deleted file mode 100644 index 57748517bc..0000000000 --- a/content/v1.12-docs/projects/istio-csr.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: istio-csr -description: '' ---- - -istio-csr is an agent that allows for [Istio](https://istio.io) workload and -control plane components to be secured using -[cert-manager](https://cert-manager.io). - -Certificates facilitating mTLS — both inter -and intra-cluster — will be signed, delivered and renewed using [cert-manager -issuers](https://cert-manager.io/docs/concepts/issuer). - -## Getting Started Guide For istio-csr - -We have [a guide](../tutorials/istio-csr/istio-csr.md) for setting up istio-csr in a fresh -[kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) cluster. - -Following the guide is the best way to see istio-csr in action. - -If you've already seen istio-csr in action or if you're experienced with running -Istio and just want quick installation instructions, read on for more details. - -## Lower-Level Details (For Experienced Istio Users) - -⚠️ The [getting started](../tutorials/istio-csr/istio-csr.md) guide is a better place if you just want to try istio-csr out! - -Running istio-csr requires a few steps and preconditions in order: - -1. A cluster _without_ Istio already installed -2. cert-manager [installed](https://cert-manager.io/docs/installation/) in the cluster -3. An `Issuer` or `ClusterIssuer` which will be used to issue Istio certificates -4. istio-csr installed (likely via helm) -5. Istio [installed](https://istio.io/latest/docs/setup/install/istioctl/) with - some custom config required, e.g. using the example config from the [repository](https://github.com/cert-manager/istio-csr/tree/main/hack). - -### Why Custom Istio Install Manifests? - -If you take a look at the contents of [the example Istio install -manifests](https://github.com/cert-manager/istio-csr/tree/main/hack) -there are a few custom configuration options which are important. - -Required changes include setting `ENABLE_CA_SERVER` to `false` and setting the `caAddress` from which Istio will -request certificates; replacing the CA server is the whole point of istio-csr! - -Mounting and statically specifying the root CA is also an important recommended step. Without a manually specified -root CA istio-csr defaults to trying to discover root CAs automatically, which could theoretically lead to a -[signer hijacking attack](https://github.com/cert-manager/istio-csr/issues/103#issuecomment-923882792) if for example -a signer's token was stolen (such as the cert-manager controller's token). - -### Issuer or ClusterIssuer? - -Unless you know you need a `ClusterIssuer` we'd recommend starting with an `Issuer`, since it should be easier to reason about -the access controls for an Issuer; they're namespaced and so naturally a little more limited in scope. - -That said, if you view your entire Kubernetes cluster as being a trust domain itself, then a ClusterIssuer is the more natural -fit. The best choice will depend on your specific situation. - -Our [getting started guide](../tutorials/istio-csr/istio-csr.md) uses an `Issuer`. - -### Which Issuer Type? - -Whether you choose to use an `Issuer` or a `ClusterIssuer`, you'll also need to choose the type of issuer you want such as: - -- [CA](https://cert-manager.io/docs/configuration/ca/) -- [Vault](https://cert-manager.io/docs/configuration/vault/) -- or an [external issuer](https://cert-manager.io/docs/configuration/external/) - -The key requirement is that arbitrary values can be placed into the `subjectAltName` (SAN) X.509 extension, since -Istio places SPIFFE IDs there. - -That means that the ACME issuer **will not work** — publicly trusted certificates such as those issued by Let's Encrypt -don't allow arbitrary entries in the SAN, for very good reasons. - -If you're already using [HashiCorp Vault](https://www.vaultproject.io/) then the Vault issuer is an obvious choice. If -you want to control your own PKI entirely, we'd recommend the CA issuer. The choice is ultimately yours. - -### Installing istio-csr After Istio - -This is unsupported because it's exceptionally difficult to do safely. It's likely that installing istio-csr _after_ Istio isn't -possible to do without downtime, since installing istio-csr second would require a time period where all Istio sidecars trust -both the old Istio-managed CA and the new cert-manager controlled CA. - -## How Does istio-csr Work? - -istio-csr implements the gRPC Istio certificate service which authenticates, -authorizes, and signs incoming certificate signing requests from Istio -workloads, routing all certificate handling through cert-manager installed in -the cluster. - -This seamlessly matches the behavior of istiod in a typical installation, while -allowing certificate management through cert-manager. diff --git a/content/v1.12-docs/projects/trust-manager/README.md b/content/v1.12-docs/projects/trust-manager/README.md deleted file mode 100644 index 30e388fbe1..0000000000 --- a/content/v1.12-docs/projects/trust-manager/README.md +++ /dev/null @@ -1,373 +0,0 @@ ---- -title: trust-manager -description: 'Distributing Trust Bundles in Kubernetes' ---- - -trust-manager is the easiest way to manage trust bundles in Kubernetes and OpenShift clusters. - -It orchestrates bundles of trusted X.509 certificates which are primarily used for validating -certificates during a TLS handshake but can be used in other situations, too. - -## Overview - -trust-manager is a small Kubernetes operator which aims to help reduce the overhead of managing -TLS trust bundles in your clusters. - -It adds the `Bundle` custom Kubernetes resource (CRD) which can read input from various sources -and combine the resultant certificates into a bundle ready to be used by your applications. - -trust-manager ensures that it's both quick and easy to keep your trusted certificates up-to-date -and enables cluster administrators to easily automate providing a secure bundle without having -to worry about rebuilding containers to update trust stores. - -It's designed to complement cert-manager and works well when consuming CA certificates from a -cert-manager `Issuer` or `ClusterIssuer` but can be used entirely independently from cert-manager -if needed. - -## Usage - -trust-manager is intentionally simple, and adds one new Kubernetes `CustomResourceDefintion`: `Bundle`. - -A `Bundle` represents a set of PEM-encoded X.509 certificates that should be distributed and made -available across the cluster. `Bundle`s are cluster scoped. - -Users specify a list of `sources`, which trust-manager will query and concatenate certificate data from. -The only other required field is the `target`, which describes how and where the resulting bundle will -be written. - -An example `Bundle` might look like this: - -```yaml -apiVersion: trust.cert-manager.io/v1alpha1 -kind: Bundle -metadata: - name: my-org.com # The bundle name will also be used for the target -spec: - sources: - # Include a bundle of publicly trusted certificates which can be - # used to validate most TLS certificates on the internet, such as - # those issued by Let's Encrypt, Google, Amazon and others. - - useDefaultCAs: true - - # A Secret in the trust-manager namespace - - secret: - name: "my-db-tls" - key: "ca.crt" - - # A ConfigMap in the trust-manager namespace - - configMap: - name: "my-org.net" - key: "root-certs.pem" - - # A manually specified string - - inLine: | - -----BEGIN CERTIFICATE----- - MIIC5zCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl - .... - 0V3NCaQrXoh+3xrXgX/vMdijYLUSo/YPEWmo - -----END CERTIFICATE----- - target: - # Data synced to the ConfigMap `my-org.com` at the key `root-certs.pem` in - # every namespace that has the label "linkerd.io/inject=enabled". - configMap: - key: "root-certs.pem" - namespaceSelector: - matchLabels: - linkerd.io/inject: "enabled" -``` - -`Bundle` resources currently support several source types: - -- `configMap` - a `ConfigMap` resource in the trust-manager namespace -- `secret` - a `Secret` resource in the trust-manager namespace -- `inLine` - a manually specified string containing at least one certificate -- `useDefaultCAs` - usually, a bundle of publicly trusted certificates - -These sources, along with the single currently supported target type (`configMap`) -are documented in the trust-manager [API reference documentation](./api-reference.md). - -#### Namespace Selector - -A target's `namespaceSelector` is used to restrict which Namespaces your `Bundle`'s target -should be synced to. - -`namespaceSelector` supports the field `matchLabels`. - -Please see [Kubernetes documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) -for more information about how label selectors can be configured. - -If `namespaceSelector` is empty, a `Bundle`'s target will be synced to all Namespaces. - -> ⚠️ A future update to trust-manager **will** change this behavior so that an empty namespace selector will sync only -to the trust-manager namespace by default. - -## Installation - -### Helm - -Helm is the easiest way to install trust-manager and comes with a publicly trusted certificate bundle package -(for the`useDefaultCAs` source) derived from Debian containers. - -When installed via Helm, trust-manager has a dependency on cert-manager for provisioning an application certificate, -and as such trust-manager is also installed into the cert-manager namespace. - -```bash -helm repo add jetstack https://charts.jetstack.io --force-update -helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set installCRDs=true --wait --create-namespace -helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --wait -``` - -### Manual Installation - -We strongly recommend that you install trust-manager using Helm and we don't currently support manually installed -versions of trust-manager. This is so that we can focus on continuing to improve trust-manager with the resources -we currently have available. - -## Quick Start Example - -Let's get started with an example of creating our own `Bundle`! - -First we'll create a demo cluster: - -```bash -git clone https://github.com/cert-manager/trust-manager trust-manager -cd trust-manager -make demo -``` - -Once we have a running cluster, we can create a `Bundle` using the default CAs which were configured -when trust-manager started up. Since we've installed trust-manager using Helm, our default CA package -contains publicly trusted certificates derived from a Debian container. - -```bash -kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - < 🤔 Wondering why we used `tls.crt` and not `ca.crt`? More details [below](./README.md#preparing-for-production). - -Finally, we'll update our `Bundle` to include our new private CA: - -```bash -kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - < ⚠️ This upgrade process assumes that it's the only thing running. If another user or process changes Helm values -> while you're doing this process, you might overwrite their work. - -First, we'll dump our current Helm values, so we don't lose them: - -```bash -helm get values -n cert-manager trust-manager -oyaml > values.yaml -``` - -Next, if `defaultPackageImage.tag` is already set in `values.yaml`, update it. Otherwise, add it. -You can find the available tags [on `quay.io`](https://quay.io/repository/jetstack/cert-manager-package-debian?tab=tags&tag=latest). - -```yaml -# values.yaml -... -defaultPackageImage: - tag: XYZ -``` - -These versions of the default package image tags are derived directly from the version of the `ca-certificates` package in Debian. - -Finally, apply back the changes, being sure to manually specify the version of trust-manager which is installed, to avoid -also updating the trust-manager controller at the same as the default CA package: - -```bash -# Get the currently installed version. You could do this manually if you find that easier. -TRUST_MANAGER_VER=$(helm list --filter "^trust-manager$" -n cert-manager -ojson | jq -r ".[0].app_version") - -# Check the version makes sense -echo $TRUST_MANAGER_VER - -# Run the upgrade -helm upgrade -f values.yaml -n cert-manager trust-manager jetstack/trust-manager --version $TRUST_MANAGER_VER -``` - -If an incorrect tag is used, your deployment will fail and you'll likely need to use `helm rollback` to get back -to a working state. - -## Preparing for Production - -TLS can be complicated and there are many ways to misuse TLS certificates. - -Here are some potential gotchas here to be aware of before running trust-manager in production. - -If you're planning on running trust-manager in production and you're using more than just the default CA package, -we **strongly** advise you to read and understand this section. It could save you from causing an outage later. - -> ℹ️ These gotchas aren't specific to trust-manager and you could run into any of them with any method of managing TLS trust! - -### Bundling Intermediates - -If you've ever used a Let's Encrypt client such as [Certbot](https://certbot.eff.org/) you'll probably have -seen that it generates several certificate files, such as `cert.pem`, `chain.pem`, and `fullchain.pem`. - -These various files are provided to support various different applications, which might require the certificate -and the chain to be given separately. For most users and applications `fullchain.pem` is the only correct choice. - -Unfortunately the existence of these files has the unfortunate side effect of people sometimes assuming that `cert.pem` -is the correct choice even when `fullchain.pem` would be correct. This means that the rest of the chain will not -be sent when the certificate is used. - -Often, a quick fix that _seems_ to work for this is that clients add the chain to their trust store, which will seem -to fix certificate errors in the short term. It's easy for this kind of "fix" to end up being embedded somewhere as a -solution which others can follow. - -This "fix" is dangerous; it means that the intermediate cannot be safely rotated without all trust stores -which contain it being updated first. - -Intermediates in this case become _de facto_ root certificates, which completely defeats the point of having -intermediate certificates in the first place. - -Avoid using intermediates in any trust store wherever possible unless you're absolutely certain they should be included. -An example of where it might be OK would be cross signing, which is not likely to be required in the general case. - -It would be better to copy just the root certificate to a new `ConfigMap` and use that as a source rather than trusting -an intermediate. - -### cert-manager Integration: `ca.crt` vs `tls.crt` - -If you're pointing trust-manager at a `Secret` containing a cert-manager-issued certificate, you'll see two relevant -fields: `ca.crt` and `tls.crt`. (We're ignoring `tls.key` - trust-manager definitely doesn't need to access that) - -That leads to an obvious question: between `ca.crt` and `tls.crt`, which should I use for trust-manager? - -Unfortunately, it's impossible to say in the general case which field is correct to use, but we can provide guidelines. - -`tls.crt` will generally contain multiple certificates which may not all be issuers and some of which are likely to be -intermediate certificates. If that's the case, you shouldn't use `tls.crt` as a source. (See "Bundling Intermediates" above for details.) - -`ca.crt` might then seem like the more generally correct choice but it's important to bear in mind that it can only ever -be populated on a best-effort basis. The contents of `ca.crt` depend on the `Issuer` being configured correctly, and some -issuer types may not ever be able to provide a useful or correct entry for this field. - -As a rule, you should prefer to create `Bundles` exclusively using root certificates (again, see above), and so you should -only use whichever field has a single root certificate in it. Consider reading below about why you might not want to -actually rely directly on cert-manager-issued certificates. - -### cert-manager Integration: Intentionally Copying CA Certificates - -It's very strange in the Kubernetes world to suggest intentionally adding a step which seems to make automating infrastructure -harder, but in the case of TLS trust stores it can be a wise choice. - -Say you have a cert-manager `Issuer` which has the root certificate you want to trust in `ca.crt`. It's tempting to -use the `Secret` directly and point at `ca.crt`, but a best practice would be to copy that root into a separate `ConfigMap` -(or `Secret`). - -The reason is - as with many TLS gotchas - certificate rotation. If you rotate your issuer such that it's issued from a new root -certificate, trust-manager will see the `Secret` be updated and automatically update your trust bundle to include the new root - -immediately distrusting the old root. - -That means that if any services were still using a certificate issued by the old root, they'll be distrusted and will break. - -Rotation requires that both root certificates are trusted simultaneously for a period, or else that all issued certificates -are rotated either before or at the same time as the old root. - -## Known Issues - -### `kubectl describe` - -The `useDefaultCAs` option hits a corner case inside `kubectl describe` and is rendered as `Use Default C As: true`. This is -purely cosmetic. diff --git a/content/v1.12-docs/projects/trust-manager/api-reference.md b/content/v1.12-docs/projects/trust-manager/api-reference.md deleted file mode 100644 index 994e70ce3b..0000000000 --- a/content/v1.12-docs/projects/trust-manager/api-reference.md +++ /dev/null @@ -1,478 +0,0 @@ ---- -title: trust-manager API Reference -description: "trust-manager API documentation for custom resources" ---- - -Packages: - -- [`trust.cert-manager.io/v1alpha1`](#trustcert-manageriov1alpha1) - -# `trust.cert-manager.io/v1alpha1` - -Resource Types: - - -- [Bundle](#bundle) - - - - -## `Bundle` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              apiVersionstringtrust.cert-manager.io/v1alpha1true
              kindstringBundletrue
              metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
              specobject - Desired state of the Bundle resource.
              -
              true
              statusobject - Status of the Bundle. This is set and managed automatically.
              -
              false
              - - -### `Bundle.spec` - - -Desired state of the Bundle resource. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              sources[]object - Sources is a set of references to data whose data will sync to the target.
              -
              true
              targetobject - Target is the target location in all namespaces to sync source data to.
              -
              true
              - - -### `Bundle.spec.sources[index]` - - -BundleSource is the set of sources whose data will be appended and synced to the BundleTarget in all Namespaces. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              configMapobject - ConfigMap is a reference to a ConfigMap's `data` key, in the trust Namespace.
              -
              false
              inLinestring - InLine is a simple string to append as the source data.
              -
              false
              secretobject - Secret is a reference to a Secrets's `data` key, in the trust Namespace.
              -
              false
              useDefaultCAsboolean - UseDefaultCAs, when true, requests the default CA bundle to be used as a source. Default CAs are available if trust-manager was installed via Helm or was otherwise set up to include a package-injecting init container by using the "--default-package-location" flag when starting the trust-manager controller. If default CAs were not configured at start-up, any request to use the default CAs will fail. The version of the default CA package which is used for a Bundle is stored in the defaultCAPackageVersion field of the Bundle's status field.
              -
              false
              - - -### `Bundle.spec.sources[index].configMap` - - -ConfigMap is a reference to a ConfigMap's `data` key, in the trust Namespace. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              namestring - Name is the name of the source object in the trust Namespace.
              -
              true
              - - -### `Bundle.spec.sources[index].secret` - - -Secret is a reference to a Secrets's `data` key, in the trust Namespace. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              namestring - Name is the name of the source object in the trust Namespace.
              -
              true
              - - -### `Bundle.spec.target` - - -Target is the target location in all namespaces to sync source data to. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              configMapobject - ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
              -
              false
              namespaceSelectorobject - NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
              -
              false
              - - -### `Bundle.spec.target.configMap` - - -ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              - - -### `Bundle.spec.target.namespaceSelector` - - -NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              matchLabelsmap[string]string - MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
              -
              false
              - - -### `Bundle.status` - - -Status of the Bundle. This is set and managed automatically. - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              conditions[]object - List of status conditions to indicate the status of the Bundle. Known condition types are `Bundle`.
              -
              false
              defaultCAVersionstring - DefaultCAPackageVersion, if set and non-empty, indicates the version information which was retrieved when the set of default CAs was requested in the bundle source. This should only be set if useDefaultCAs was set to "true" on a source, and will be the same for the same version of a bundle with identical certificates.
              -
              false
              targetobject - Target is the current Target that the Bundle is attempting or has completed syncing the source data to.
              -
              false
              - - -### `Bundle.status.conditions[index]` - - -BundleCondition contains condition information for a Bundle. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              statusstring - Status of the condition, one of ('True', 'False', 'Unknown').
              -
              true
              typestring - Type of the condition, known values are (`Synced`).
              -
              true
              lastTransitionTimestring - LastTransitionTime is the timestamp corresponding to the last status change of this condition.
              -
              - Format: date-time
              -
              false
              messagestring - Message is a human readable description of the details of the last transition, complementing reason.
              -
              false
              observedGenerationinteger - If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Bundle.
              -
              - Format: int64
              -
              false
              reasonstring - Reason is a brief machine readable explanation for the condition's last transition.
              -
              false
              - - -### `Bundle.status.target` - - -Target is the current Target that the Bundle is attempting or has completed syncing the source data to. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              configMapobject - ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
              -
              false
              namespaceSelectorobject - NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
              -
              false
              - - -### `Bundle.status.target.configMap` - - -ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              - - -### `Bundle.status.target.namespaceSelector` - - -NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              matchLabelsmap[string]string - MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
              -
              false
              diff --git a/content/v1.13-docs/contributing/README.md b/content/v1.13-docs/contributing/README.md deleted file mode 100644 index aec6b21254..0000000000 --- a/content/v1.13-docs/contributing/README.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: Contributing -description: 'cert-manager contributing guide: Get involved!' ---- - -## Great to See You! - -Whether you're a previous contributor or a first timer looking to get involved, we love -it when the community comes together to improve the project! - -In this "contributing" section we document processes we follow as a project, and include -some details on how to build, test and run cert-manager for development purposes. - -## Meetings - -All cert-manager meetings are open for everyone to join; if you have a question or a suggestion or just want to chat, -please feel free to come along and get involved! - -To get invites you can subscribe to [our mailing list](https://groups.google.com/forum/#!forum/cert-manager-dev) and -you should receive calendar invites by mail shortly after joining. The complexities of calendars mean that some invites -might be sent multiple times depending on your email and calendar providers and you might get some invites for past -or future meetings which have been rescheduled or edited. Sorry about that! - -We have 2 regular repeating meetings: our quick daily check-in and an hour-long community meeting every two weeks. - -If you're having any issues joining our meetings, ensure that you're part of the [`cert-manager-dev`](https://groups.google.com/forum/#!forum/cert-manager-dev) Google group, and always feel free to ask in [Slack](./#slack) for help! - -
              -🔰 All of our meetings happen on London (UK) time; you can add London to the world clocks on your phone to avoid confusion! - -When daylight savings time changes in London might be different to when it changes for you if you live in a place that either -doesn't have DST or which changes on a different schedule like North America or Australia! -
              - -### Daily Check-In - -Our daily check-in meetings [happen on Google Meet](https://meet.google.com/eum-fyvt-xpa) at [10:30 London time](http://www.thetimezoneconverter.com/?t=10:30&tz=Europe/London) every weekday. - -The format is a 5 minute social chat, followed by a quick round-robin status report and ending with any longer form talking points. - -The status report is a stand-up where we talk about work done yesterday, work coming up and highlight any blockers. -We'll try to keep to a **strict time limit** during these status reports of around 1 minute per person. - -Please don't be offended if someone steps in when you run out of time and moves the reports along to the next person - the idea -is for everyone to be succinct so it's clear what's being worked on and who is blocked. - -We finish with talking points, which are open-ended discussions on any topic related to cert-manager or its sub-projects. -We'll ensure that anyone outside of the core maintainer team who has a talking point goes first. - -### Community Meetings - -Our bi-weekly (i.e. every two weeks) community meetings happen [on Google Meet](https://meet.google.com/iga-jwvx-nye) at [17:00 London time](http://www.thetimezoneconverter.com/?t=17:00&tz=Europe/London) (for dates, check calendar invites or [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U)). - -These meetings are an hour-long chat about cert-manager topics. It's a great way to get involved with contributing for the -first time; to get answers to any questions you might have; or to propose a new feature which needs some explanation. - -If you want to discuss something, please add it to the [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U) -before the meeting. The meeting chair will try to get to everything that was on the notes before the meeting started. - -We try to record these meetings and put them on YouTube so they can be checked later - if you don't want to appear on video please keep -your camera off! - -## Slack - -We have two cert-manager channels on [Kubernetes Slack](https://slack.k8s.io) which we use to chat: - -* [`cert-manager`](https://kubernetes.slack.com/messages/cert-manager): for all users of cert-manager; use this one for any usage related questions -* [`cert-manager-dev`](https://kubernetes.slack.com/messages/cert-manager-dev): for collaboration between cert-manager contributors and maintainers; please only use this for code related questions diff --git a/content/v1.13-docs/contributing/building.md b/content/v1.13-docs/contributing/building.md deleted file mode 100644 index 92fd7a996f..0000000000 --- a/content/v1.13-docs/contributing/building.md +++ /dev/null @@ -1,267 +0,0 @@ ---- -title: Building cert-manager -description: 'cert-manager contributing guide: Building cert-manager' ---- - -cert-manager is built and tested using [make](https://www.gnu.org/software/make/manual/make.html), with a focus on using the standard Go tooling -where possible and keeping system dependencies to a minimum. The cert-manager build system can provision most of its dependencies - including Go - -automatically if required. - -cert-manager's build system fully supports developers who use `Linux amd64`, `macOS amd64` and `macOS arm64`. - -It also has limited support for `Linux arm64`, although that platform is largely untested and isn't fully supported. - -Other operating systems and architectures may work but will likely require hacks and workarounds to develop on. - -## Prerequisites - -There are very few other requirements needed for developing cert-manager, and crucially the build system should tell you with a friendly error -message if there's anything missing. If you think an error message which relates to a missing dependency is unhelpful, we consider that a bug and -we'd appreciate if you raised [an issue](https://github.com/cert-manager/cert-manager/issues/new?assignees=&labels=&template=bug.md) to tell us about it! - -You should install the following tools before you start developing cert-manager: - -- [git](https://git-scm.com/) -- [curl](https://curl.se/) -- [GNU make](https://www.gnu.org/software/make/manual/make.html), `v3.82` or newer -- GNU Coreutils (usually already installed on Linux, available via [homebrew](https://formulae.brew.sh/formula/coreutils) for macOS) -- `jq` (available in Linux package managers and in [homebrew](https://formulae.brew.sh/formula/jq)) -- `docker` (or `podman`, see [Container Engines](#container-engines) below) -- `Go` (optional; see [Go Versions](#go-versions) below) - -## Getting Started - -The vast majority of commands which you're likely to need to use are documented via `make help`. That's probably the first place to start if you're -developing cert-manager. We'll also provide an overview on this page of some of the key targets and things to bear in mind. - -### Go Versions - -cert-manager defaults to using whatever version of Go you've installed locally on your system. If you want to use your system Go, that's totally fine. - -Alternatively, make can provision and "vendor" Go specifically for cert-manager, helping to ensure you use the same version that's used in CI and to -make it easier to get started developing. - -To start using a vendored Go, run: `make vendor-go`. - -You only need to run `vendor-go` once and it'll be "sticky", being used for all future make invocations in your local checkout. - -To return to using your system version of go, run: `make unvendor-go`. - -To check which version of Go is _currently_ being used, run: `make which-go`, which prints the version number of Go and the path to the Go binary. - -```console -# Use a vendored version of go -$ make vendor-go -cd _bin/tools/ && ln -f -s ../downloaded/tools/_go-1.XY.Z-linux-amd64/goroot . -cd _bin/tools/ && ln -f -s ../downloaded/tools/_go-1.XY.Z-linux-amd64/goroot/bin/go . - -# A path to go inside the cert-manager directory indicates that a vendored Go is being used -$ make which-go -go version go1.XY.Z linux/amd64 -go binary used for above version information: /home/user/workspace/cert-manager/_bin/tools/go - -# Go back to the system Go -$ make unvendor-go -rm -rf _bin/tools/go _bin/tools/goroot - -# The binary is now "go" which should be found in $PATH -$ make which-go -go version go1.AB.C linux/amd64 -go binary used for above version information: go -``` - -### Go Workspaces - -In short: Some development tools will complain about cert-manager's module layout; to help with this, generate a -`go.work` file using `make go-workspace`. - -The cert-manager repository as of cert-manager 1.12 contains multiple Go modules, in a setup where only the core module `github.com/cert-manager/cert-manager` -is expected to be imported by third party modules. There are separate modules (which we call submodules), all of which have replace statements for the core -cert-manager module. - -This setup is intentional to convey that these submodules are not intended to be imported by third parties, and to ensure that each submodule always uses -whatever the cert-manager core module version is at the same commit - but this structure can have the side effect that certain development tools and scripts -will not work as expected. - -As an example, `go test ./...` will by default only affect the core module. To test, say, the controller, you'd need to use `cd cmd/controller && go test ./...`. - -This can be avoided through the use of go workspaces, which will handle local replacements for you and work better with editors such as VS Code. - -You can run `make go-workspace` to generate a `go.work` file which should enable `go test ./...` to work across the -whole repo, and which should help editors to understand the module structure. - -Note that go workspaces are not used when testing pull requests in CI. If you see errors in CI which you can't replicate -locally, try building with the `GOWORK` environment variable set to `off` or deleting the `go.work` file. - -### Parallelism - -The cert-manager Makefile is designed to be highly parallel wherever possible. Any build and test commands should be able to be executed in parallel using -standard Make functionality. - -One important caveat is that that Go will default to detecting the number of cores available on the system and spinning up as many threads as it can. If you're -using Make functionality to run multiple builds in parallel, this number of threads can be excessive and actually lead to slower builds. - -It's possible to limit the number of threads Go uses we'd generally recommend doing so when using Make parallelism. - -The best values to use will depend on your system, but we've had success using around half of the available number of cores for Make and limiting Go to between -2 and 4 threads per core. - -For example, using an 8-core machine: - -```bash -# Run 4 make targets in parallel, and limit each `go build` to 2 threads. -make GOMAXPROCS=2 -j4 release-artifacts -``` - -## Testing - -cert-manager's build pipeline and CI infrastructure uses the same Makefile that you use when developing locally, -so there should be no divergence between what the tests run and what you run. That means you should be able to be pretty confident that any changes you make -won't break when tested in CI. - -### Running Local Changes in a Cluster - -It's common that you might want to run a local Kubernetes cluster with your locally-changed copy of cert-manager in it, for manual testing. - -There are make targets to help with this; see [Developing with Kind](./kind.md) for more information. - -### Unit and Integration Tests - -First of all: If you want to test using `go test`, feel free! For unit tests (which we define as any test outside of the `test/` directory), `go test` will -work on a fresh checkout. - -Note that the cert-manager repo is split into multiple modules and unless you're using go workspaces `go test ./...` won't actually run all tests. See [Go Workspaces](./building.md#go-workspaces) above for more details. - -Integration tests may require some external tools to be set up first, so to run the integration tests inside `test/` you might need to run: - -```bash -make setup-integration-tests -``` - -Helper targets are also available which use [`gotestsum`](https://github.com/gotestyourself/gotestsum) for prettier output. It's also possible to -configure these targets to run specific tests: - -```bash -# Run all unit and integration tests -make test - -# Run only unit tests -make unit-test - -# Run only integration tests -make integration-test - -# Run all tests in pkg -make WHAT=./pkg/... test - -# Run unit and integration tests exactly as run in CI -# (NB: usually not needed - this is mostly for JUnit test output for dashboards) -make test-ci -``` - -### End-to-End Testing - -cert-manager's end-to-end tests are a little more involved and have [dedicated documentation](./e2e.md) describing their use. - -### Other Checks - -We run a variety of other tools on every Pull Request to check things like formatting, import ordering and licensing. These checks can all be run locally: - -```bash -make ci-presubmit -``` - -NB: One of these checks currently requires Python 3 to be installed, which is a unique requirement in the code base. We'd like to remove that requirement in the future. - -## Updating CRDs and Code Generation - -Changes to cert-manager's CRDs require some code generation to be done, which will be checked on every pull request. - -If you make changes to cert-manager CRDs, you'll need to run some commands locally before raising your PR. - -This is documented in our [CRDs](./crds.md) section. - -## Building - -cert-manager produces many artifacts for a lot of different OS / architecture combinations, including: - -- Container images -- Client binaries (`cmctl` and `kubectl_cert-manager`) -- Manifests (Helm charts, static YAML) - -All of these artifacts can be built locally using make. - -### Containers - -cert-manager's most important artifacts are the containers which actually run cert-manager in a cluster. We default to using `docker` for this, -but aim to support docker-compatible CLI tools such as `podman`, too. See [Container Engines](#container-engines) for more info. - -There are several targets for building different cert-manager containers locally. These will all default to using `docker`: - -```bash -# Build everything for every architecture -make all-containers - -# Build just the controller containers on every architecture -make cert-manager-controller-linux - -# As above, but for the webhook, cainjector, acmesolver and cmctl containers -make cert-manager-webhook-linux -make cert-manager-cainjector-linux -make cert-manager-acmesolver-linux -make cert-manager-ctl-linux -``` - -#### Container Engines - -NB: This section doesn't apply to end-to-end tests, which might not work outside of Docker at the time of writing. See the [end-to-end documentation](./e2e.md#container-engines) -for more information. - -It's possible to use an alternative container engine to build cert-manager containers. This has been successfully tested using `podman`. - -Configure an alternative container engine by setting the `CTR` variable: - -```bash -# Build everything for every architecture, using podman -make CTR=podman all-containers -``` - -### Client Binaries - -Both `cmctl` and `kubectl_cert-manager` can be built locally for a release. These binaries are built for Linux, macOS and Windows across several architectures. - -```bash -# Build all cmctl binaries for all platforms, then for linux only, then for macOS only, then for Windows only -make cmctl -make cmctl-linux -make cmctl-darwin -make cmctl-windows - -# As above but for kubectl_cert-manager -make kubectl_cert-manager -make kubectl_cert-manager-linux -make kubectl_cert-manager-darwin -make kubectl_cert-manager-windows -``` - -### Manifests - -We use "manifests" as a catch-all term for non-binary artifacts which we build as part of a release including static installation YAML and our Helm chart. - -Everything can be built using make: - -```bash -make helm-chart -make static-manifests -``` - -### Everything - -Sometimes it's useful to build absolutely everything locally, to be sure that a change didn't break some obscure architecture and to build confidence when raising a PR. - -It's not easy to build a _complete_ release locally since a full release includes signatures which depend on KMS keys being configured. Most users probably don't -need that, though, and for this use case there's a make target which will build everything except the signed artifacts: - -```bash -make GOMAXPROCS=2 -j4 release-artifacts -``` diff --git a/content/v1.13-docs/contributing/coding-conventions.md b/content/v1.13-docs/contributing/coding-conventions.md deleted file mode 100644 index 8001cc387d..0000000000 --- a/content/v1.13-docs/contributing/coding-conventions.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Coding Conventions -description: 'cert-manager contributing guide: Coding conventions' ---- - -cert-manager, like most Go projects, delegates almost all stylistic choices to `gofmt`, -with `goimports` on top for organizing imports. Broadly speaking, if you set your editor to run -`goimports` when you save a file, your code will be stylistically correct. - -cert-manager generally also follows the Kubernetes -[coding conventions](https://www.kubernetes.dev/docs/guide/coding-convention/) and the Google -[Go code review comments](https://github.com/golang/go/wiki/CodeReviewComments). - -## Organizing Imports - -Imports should be organized into 3 blocks, with each block separated by two newlines: - -```go -import ( - "stdlib" - - "external" - - "internal" -) -``` - -An example might be the following, taken from -[`pkg/acme/accounts/client.go`](https://github.com/cert-manager/cert-manager/blob/0c71fe7795858b96cabcddabf706d997cd2fba3f/pkg/acme/accounts/client.go): - -```go -import ( - "crypto/rsa" - "crypto/tls" - "net" - "net/http" - "time" - - acmeapi "golang.org/x/crypto/acme" - - acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" - acmeutil "github.com/cert-manager/cert-manager/pkg/acme/util" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - "github.com/cert-manager/cert-manager/pkg/metrics" - "github.com/cert-manager/cert-manager/pkg/util" -) -``` - -Once this manual split of standard library, external and internal imports has been made, it will be -enforced automatically by `goimports` when executed in the future. - -## UK vs. US spelling - -For the sake of consistency, cert-manager uses en-US spelling for the -documentation in https://cert-manager.io as well as within the cert-manager -codebase. A comprehensive list of en-GB → en-US word substitution is available -on Ubuntu's -[`WordSubstitution`](https://wiki.ubuntu.com/EnglishTranslation/WordSubstitution) -page. \ No newline at end of file diff --git a/content/v1.13-docs/contributing/contributing-flow.md b/content/v1.13-docs/contributing/contributing-flow.md deleted file mode 100644 index bd5dff0669..0000000000 --- a/content/v1.13-docs/contributing/contributing-flow.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: Contributing Flow -description: 'cert-manager contributing guide: Contribution flow' ---- - -All of cert-manager's development is done via -[GitHub](https://github.com/cert-manager/cert-manager) which contains code, issues and pull -requests. - -All code for the documentation and cert-manager.io can be found at [the cert-manager/website repo](https://github.com/cert-manager/website/). -Any issues towards the documentation should also be filed there. - -## GitHub bot - -We use [Prow](https://github.com/k8s-ci-robot/test-infra/tree/master/prow) on all our repositories. -If you've ever looked at a Kubernetes repo, you will probably already have met Prow. Prow will be able to help you in GitHub using its commands. -You can find then all [on the command help page](https://prow.build-infra.jetstack.net/command-help). -Prow will also run all tests and assign certain labels on PRs. - -## Bugs - -All bugs should be tracked as issues inside the -[GitHub](https://github.com/cert-manager/cert-manager/issues) repository. Issues should then be -attached with the `kind/bug` tag. To do this add `/kind bug` to your issue description. -This may then be assigned a priority and milestone to be addressed in a future release. - -The more logs and information you can give about what and how the bug has been -discovered, the faster it can be resolved. - -Critical bug fixes are typically also cherry picked to the current minor stable releases. - -> Note: If you are simply looking for _troubleshooting_ then you should post -> your question to the community `cert-manager` [slack channel](https://slack.k8s.io). -> Many more people read this channel than GitHub issues, it's likely your problem will -> be solved quicker by using Slack. -> Please also check that the bug has not already been filed by searching for key -> terms in the issue search bar. - -### (Re)opening and closing issues - -Prow can assist you to reopen or close issues you file, you can trigger it using `/reopen` or `/close` in a GitHub Issue comment. - -## Features - -Feature requests should be created as -[GitHub](https://github.com/cert-manager/cert-manager/issues) issues. They should contain -clear motivation for the feature you wish to see as well as some possible -solutions for how it can be implemented. -Issues should then be tagged with `kind/feature`. To do this add `/kind feature` to your issue description. - -> Note: It is often a good idea to bring your feature request up on the -> community `cert-manager` [slack channel](https://slack.k8s.io) to discuss whether -> the feature request has already been made or is aligned with the project's -> priorities. - -## Creating Pull Requests - -Changes to the cert-manager code base is done via [pull -requests](https://github.com/cert-manager/cert-manager/pulls). Each pull request -should ideally have a corresponding issue attached that is to be fixed by this -pull request. It is valid for multiple pull requests to resolve a single issue -in the interest of keeping code changes self contained and simpler to review. - -Once created, a team member will assign themselves for review and enable -testing. To make sure the changes get merged, keep an eye out for reviews which -can have multiple cycles. - -If the pull request is a critical bug fix then this will probably -also be cherry picked to the current stable version of cert-manager as a patch -release. - -To let people know that your PR is still a work in progress, we usually add a -`WIP:` prefix to the title of the PR. Prow will then automatically set the label -`do-not-merge/work-in-progress`. - -### Release Note Guidelines - -The `release-note` code block in the PR description aims at explaining to the -end-user whether they should care about this change and whether they will be -affected. It is OK to have multiple sentences as long as it is written in good -English (subject verb complement, ends with a dot). - -A release note block shouldn't just paraphrase the commit message. For example, -the end-user doesn't know what "comparisons" are and which custom resources are -affected: - -~~~markdown -```release-note -Adds missing comparisons for certain fields which were incorrectly skipped if a LiteralSubject was set -``` -~~~ - -A better release note block gives context and specifically tells the end-user -how they will be affected: - -~~~markdown -```release-note -When using the `literalSubject` on a Certificate, the IPs, URIs, DNS names, and email addresses subject segments are now properly compared. -``` -~~~ - -New lines in the release note block translate to hard line breaks, which means -it is not possible to soft-wrap the release note. A new line can be useful for -lists: - -~~~markdown -```release-note -cainjector: -- New flags were added to the cainjector binary. They can be used to modify what injectable kinds are enabled. If cainjector is only used as a cert-manager's internal component it is sufficient to only enable validatingwebhookconfigurations and mutatingwebhookconfigurations injectable resources; disabling the rest can improve memory consumption. By default all are enabled. -- The `--watch-certs` flag was renamed to `--enable-certificates-data-source`. -``` -~~~ - -If this PR introduces a breaking change, the release note block must start with -`**BREAKING:**` (note the bold text). - -### Cherry Picking - -If the pull request contains a critical bug fix then this should be cherry picked in to the current stable cert-manager branch -and [released as a patch release](../installation/supported-releases.md#support-policy). - -To trigger the cherry-pick process, add a comment to the GitHub PR. -For example: -``` -/cherry-pick release-x.y -``` - -The `jetstack-bot` will then create a new branch and a PR against the release branch, -which should be reviewed, approved and merged using the process described above. - -### DCO signoff - -All commits in the PR should be signed off, more info on how to do this is at the [DCO Sign Off](./sign-off.md) page. -Exceptions can only be made for small documentation fixes. - -## Project Management - -Most of cert-manager's project management is done on GitHub, with the help of Prow. - -### When will something be released? - -Our team works using [GitHub milestones](https://github.com/cert-manager/cert-manager/milestones). -When a milestone is set on an Issue it is generally an indication of when we plan to address this. -Prow will apply milestones on merged PRs, this will tell you in which version that PR will land. - -The milestone page will also have an indicated due date when we will release. This might have some delay. -We brief our users/contributors about this in our bi-weekly community meeting, for an up to date status report we recommend joining these. - -### Labels - -We make a heavy use of GitHub labels for PRs and Issues. The ones on PRs are mostly managed by Prow and code reviewers. -In issues we always aim to add 3 types: area, priority and kind. These are set using Prow using `/area`, `/kind` and `/priority`. -Sometimes `/triage` is also added which helps us when following up Issues. - -* Area indicates the code area which is/will need changing -* Kind indicates if it is a `bug` or a `feature` but also can be `documentation` or `cleanup` (general maintenance) -* Priority is the priority it has for the cert-manager team, PRs are still very welcome for those! - -### Assignees meaning in PRs and issues - -Sometimes, you might see someone commenting with the -[`/assign` prow command](https://prow.build-infra.jetstack.net/command-help#assign): - -```plain -/assign @meyskens -``` - -Here is the meaning that we give to the GitHub assignees: - -- On issues, it means that the assignee is working on it. -- On PRs, we use it as a way to know who should be taking a look at the PR at any time: - - When the author is assigned, it means the PR needs work to be done aka "changes requested"; - - When nobody is assigned, it means this PR needs review; - - When someone different from the author is assigned, it means this person is reviewing this PR. - -### Triage Party! - -Every few weeks we will plan a Triage Party meeting, where we use the (Triage Party)[https://triage.build-infra.jetstack.net/] tool to go recent/old issues to prioritise them so we can address them in a timely matter. These meetings are open to everyone and invites will be sent out using our mailing list (warning: despite the word party these meetings are sometimes boring). \ No newline at end of file diff --git a/content/v1.13-docs/contributing/crds.md b/content/v1.13-docs/contributing/crds.md deleted file mode 100644 index 1cce203032..0000000000 --- a/content/v1.13-docs/contributing/crds.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: CRDs -description: 'cert-manager contribution guide: CRDs' ---- - -cert-manager uses [Kubernetes Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) to define -the resources which users interact with when using cert-manager, such as `Certificate`s and `Issuer`s. - -When changes are made to the CRDs in code, there are a couple of extra steps which are required. - -## Generating CRD Updates - -We use [`controller-gen`](https://book.kubebuilder.io/reference/controller-gen.html) to update our CRDs, and [`k8s-code-generator`](https://github.com/kubernetes/code-generator) -for code generation. - -Verifying and updating CRDs and generated code can be done entirely through make. There are two steps; one will update CRDs and one will update generated code: - -```bash -# Check that CRDs and codegen are up to date -make verify-crds verify-codegen - -# Update CRDs based on code -make update-crds - -# Update generated code based on CRD defintions in code -make update-codegen -``` - -## Versions - -cert-manager currently has a single `v1` API version for public use. - -cert-manager API types are defined in [`pkg/apis/certmanager`](https://github.com/cert-manager/cert-manager/tree/master/pkg/apis/certmanager). - -ACME related resources are in [`pkg/apis/acme`](https://github.com/cert-manager/cert-manager/tree/master/pkg/apis/acme). - -### Code Comments - -Code comments on API type fields are converted into documentation on this website as well as appearing in the output of `kubectl explain`. - -That means that `go doc`-style comments on API fields should be written to be user-facing and not developer-facing. For this reason it's also fine to break from -usual Go standards regarding code comments when editing these fields. - -### Internal API Versions - -cert-manager also has an internal API version which lives under [`internal/apis`](https://github.com/cert-manager/cert-manager/tree/master/internal/apis). - -The internal version is only used for validation and conversion and controllers should not generally use it; it's not intended to be user-friendly or stable and can change. -However all new fields also have to be added here for the conversion logic to work. - -For details on conversion and versions, see the [official Kubernetes docs for CRD versioning](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/). - -## Kubebuilder - -While cert-manager doesn't fully use Kubebuilder, CRDs can make use of special Kubebuilder flags such as [validation flags](https://book.kubebuilder.io/reference/markers/crd-validation.html). - -## Making Changes to APIs - -Please see our [API compatibility promise](../installation/api-compatibility.md) for details on which types of changes to APIs are acceptable. - -Generally, the gist is that new fields can be added but that existing fields cannot be removed. - -This also means that when a field is added to a version of the API, it's permanent and its name cannot be changed. Because of this, we try to be cautious when adding new fields. - -The same principles apply to [constants and enumerated types](https://kubernetes.io/docs/reference/using-api/deprecation-policy/#enumerated-or-constant-values). diff --git a/content/v1.13-docs/contributing/dns-providers.md b/content/v1.13-docs/contributing/dns-providers.md deleted file mode 100644 index 400f3d17ee..0000000000 --- a/content/v1.13-docs/contributing/dns-providers.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: DNS Providers -description: 'cert-manager contributing guide: Creating DNS providers' ---- - -## Creating DNS Providers - -Due to the large number of requests to support DNS providers to resolve DNS -challenges, it became impractical and infeasible to maintain and test all DNS -providers in the main cert-manager repository. - -For this reason, it was decided that new DNS providers should be supported out-of-tree -by way of external webhooks. - -To implement an external DNS provider webhook, it is recommended to base your -implementation on the [cert-manager webhook-example](https://github.com/cert-manager/webhook-example). - -There's further information available in the configuration section: - -- [ACME DNS01 via webhook](../configuration/acme/dns01/README.md#webhook) -- [Configuring an ACME issuer with external webhook](../configuration/acme/dns01/webhook.md) - -If you're struggling with creating a new DNS webhook, reach out on [Slack](./README.md#slack)! diff --git a/content/v1.13-docs/contributing/e2e.md b/content/v1.13-docs/contributing/e2e.md deleted file mode 100644 index 9e6b9e7409..0000000000 --- a/content/v1.13-docs/contributing/e2e.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -title: Running End-to-End Tests -description: 'cert-manager contribuing guide: End-to-end (E2E) tests' ---- - -cert-manager has an extensive end-to-end (e2e) test suite that verifies functionality against a -real Kubernetes cluster. - -The full end-to-end test suite can take a long time to complete and is run against every pull -request made to the cert-manager project. - -Unless you've made huge changes to the cert-manager codebase --- or to the end-to-end -tests themselves --- you probably don't _need_ to run the tests locally. If you do want to -run the tests, though, this document explains how. - -
              -The status of each commit on the master branch is reported on -[`testgrid.k8s.io`](https://testgrid.k8s.io/jetstack-cert-manager-master). Join the -[`cert-manager-dev-alerts`](https://groups.google.com/g/cert-manager-dev-alerts) -Google group to receive email notifications when tests fail. -
              - -## Requirements - -There are no special requirements for the end-to-end tests. All dependencies can be -provisioned automatically through the make build system. - -## Set up End-to-End Tests - -### Create a Cluster - -You can create a kind cluster using Make: - -```console -# Create a cluster using whatever K8s version is default, named "kind" -make e2e-setup-kind - -# Create a cluster using K8s 1.23 named "keith" -make K8S_VERSION=1.23 KIND_CLUSTER_NAME=keith e2e-setup-kind -``` - -**IMPORTANT:** the kind cluster will be set up using a specific service CIDR range to enable certain functionality in end-to-end tests. This CIDR range is not currently configurable. - -Once complete, the cluster is available via `kubectl` as you'd expect. - -### Install Test Dependencies - -There are various dependencies which the end-to-end tests require, all of which can also -be installed via Make: - -```console -make e2e-setup -``` - -If you only need to update or reinstall one of these dependencies in your test cluster, you can instead install named components explicitly to save some time. - -The most common use case for this is to **reinstall cert-manager itself**, say if you've made a change -locally and want to test that change in a cluster: - -```console -# Most important: reinstall cert-manager, including rebuilding changed containers locally -make e2e-setup-certmanager - -# An example of reinstalling something else; reinstall bind -make e2e-setup-bind - -# More generally, see make/e2e-setup.mk for different targets! -``` - -## Run End-to-End Tests - -As with setup, running tests is available through make. In fact, you can just run `make e2e` directly -and avoid having to set anything up manually! - -```console -# Set up a cluster using the defaults if one's not already present, and then run the end-to-end tests -make e2e - -# Set up a K8s 1.23 cluster and then run tests -make K8S_VERSION=1.23 e2e - -# Run tests exactly as they're run in CI; usually not needed -make e2e-ci -``` - -If you don't want to run every test you can focus on specific tests using `GINKGO_FOCUS` syntax, as described in the -[Ginkgo documentation](https://onsi.github.io/ginkgo/#focused-specs): - -```console -make GINKGO_FOCUS=".*my test description" e2e -``` - -## Cluster IP Details - -As mentioned above, the end-to-end tests expect that certain components are deployed in a -specific way and even at specific IP addresses. - -By way of illustration, the following cluster components are deployed with specific IPs: - -| Component / Make Target | Used in | IP | DNS A Record | -| -------------------------- | -------------------------- | ----------- | --------------------------------------- | -| `e2e-setup-bind` | DNS-01 tests | `10.0.0.16` | | -| `e2e-setup-ingressnginx` | HTTP-01 `Ingress` tests | `10.0.0.15` | `*.ingress-nginx.db.http01.example.com` | -| `e2e-setup-projectcontour` | HTTP-01 `GatewayAPI` tests | `10.0.0.14` | `*.gateway.db.http01.example.com` | - -If you don't set these components up correctly, you might see that the ACME HTTP01 (and other) end-to-end tests fail. - -## End-to-End Test Structure - -The end-to-end tests consist of 2 main parts: issuer specific tests and the conformance suite. - -Both parts use [Ginkgo](https://onsi.github.io/ginkgo/#getting-ginkgo) to run their tests under the hood. - -### Conformance Suite - -#### RBAC - -This suite tests all RBAC permissions granted to cert-manager on the cluster to check that it is able to operate correctly. - -#### Certificates - -This suite tests certificate functionality against all issuers. - -#### Feature Sets - -Some issuers don't support certain features, such as for example issuing Ed25519 certificates or adding an email address -to the X.509 SAN extension. - -Each test specifies a used feature using `s.checkFeatures(feature)`, which is then checked against the issuer's -`UnsupportedFeatures` list. Tests which use a feature unsupported by an issuer are skipped for that issuer. - -### Cloud Provider Tests - -The master branch of cert-manager can also be tested against different cloud providers. Currently, tests for [EKS](https://aws.amazon.com/eks/) are present which run as a periodic job once every two days. - -#### Extending The Cloud Provider Tests - -The infrastructure used to run the e2e tests on cloud providers is present in the [cert-manager/test-infra](https://github.com/cert-manager/test-infra) repository. More cloud providers can be added by creating infrastructure for them using [Terraform](https://www.terraform.io/). - -Apart from that, tests for the existing infrastructure can be customized by editing their respective prow jobs present in the [Jetstack testing repository](https://github.com/cert-manager/testing/tree/master/config/jobs/cert-manager) repository. Values like the cert-manager version or the cloud provider version are present as variables in Terraform so their values can be changed when using `terraform apply` in the prow jobs, for example, for the [EKS prow job](https://github.com/cert-manager/testing/blob/master/config/jobs/cert-manager/cert-manager-periodics.yaml#L524) the cert-manager version being tested can be changed using - -```console -terraform apply -var="cert_manager_version=v1.3.3" -auto-approve -``` - -To see a list of all configurable variables present for a particular infrastructure you can see the `variables.tf` file for that cloud provider's [infrastructure](https://github.com/cert-manager/test-infra). - -> Please note that the cloud provider tests run the e2e tests present in the **master** branch of cert-manager on a predefined version of cert-manager (can be changed in the prow job). Currently, they do **not** test code in a PR, but we have an [issue](https://github.com/cert-manager/cert-manager/issues/4349) tracking that request. diff --git a/content/v1.13-docs/contributing/external-issuers.md b/content/v1.13-docs/contributing/external-issuers.md deleted file mode 100644 index 4d18572fb3..0000000000 --- a/content/v1.13-docs/contributing/external-issuers.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: Implementing External Issuers -description: 'cert-manager contributing guide: External Issuers' ---- - -cert-manager offers a number of [core issuer types](../configuration/README.md) that represent -various certificate authorities. - -Since the number of potential issuers is larger than what could reasonably be supported in the -main cert-manager repository, cert-manager also supports out-of-tree external issuers, and treats -them the same as in-tree issuer types. - -This document is for people looking to _create_ external issuers. For more information on how to -install and configure external issuer types, read the [configuration documentation](../configuration/external.md). - -## General Overview - -An issuer represents a certificate authority that signs incoming certificate -requests. In cert-manager, the `CertificateRequest` resource represents a single -request for a signed certificate, containing the raw certificate request PEM -data as well as other information relating to the desired certificate. - -In cert-manager, each issuer type has its own controller that watches these -`CertificateRequest` resources and checks to see if a given `CertificateRequest` is -configured to use the issuer. - -This is done via the `issuerRef` stanza on the `CertificateRequest` which contains -an issuer `name`, `kind` and `group`. - -`group` denotes an API group such as `cert-manager.io` (which is responsible for all core issuer types). - -`kind` denotes the "kind" resource type of the issuer - usually `Issuer` or `ClusterIssuer`. - -`name` denotes the name of the issuer resource of the specified kind. An example might be `my-ca-issuer`. - -When an issuer controller observes a new `CertificateRequest` which refers to it, -it then ensures that the corresponding issuer resource exists in Kubernetes. - -It then uses the information inside the issuer resource to attempt to create a -signed certificate, based upon the information inside the certificate request. - -## Sample External Issuer - -If you want to create an External Issuer, the best place to start is likely to be the [Sample External Issuer](https://github.com/cert-manager/sample-external-issuer). - -The Sample External Issuer is maintained by the cert-manager team, and its README file has step-by-step instructions -on how to write an external issuer using Kubebuilder and controller-runtime. - -## Approval - -Before signing a certificate, Issuers **must** also ensure that the `CertificateRequest` is -[`Approved`](../concepts/certificaterequest.md#approval). - -If the `CertificateRequest` is not `Approved`, the issuer **must** not process it. Issuers are not -responsible for approving `CertificateRequests` and should refuse to proceed if they find a certificate -that is not approved. - -If a `CertificateRequest` created for an issuance associated with a `Certificate` gets [`Denied`](../concepts/certificaterequest.md#approval), the issuance will be failed by cert-manager's issuing controller. - -## Conditions - -Once a signed certificate has been gathered by the issuer controller, it updates the status of the -`CertificateRequest` resource with the signed certificate. It is then important to update the condition -status of that resource to a ready state, as this is what is used to signal to higher order -controllers - such as the `Certificate` controller - that the resource is ready to be consumed. - -Conversely, if the `CertificateRequest` fails, it is as important to mark the resource as such, as this will -also be used as a signal to higher order controllers. Valid condition states are listed under [concepts](../concepts/certificaterequest.md#conditions). - -## Implementation - -It is recommended that you make use of the [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder) project in order -to implement your external issuer controller. This makes it very simple to generate `CustomResourceDefinitions` and gives -you a lot of controller functionality out of the box. - -If you have further questions on how to implement an external issuer controller, it is best to reach out on [slack](./README.md#slack) -or to join a [community calls](./README.md#meetings). diff --git a/content/v1.13-docs/contributing/featuregates.md b/content/v1.13-docs/contributing/featuregates.md deleted file mode 100644 index 4e6f332967..0000000000 --- a/content/v1.13-docs/contributing/featuregates.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -Title: Implementing feature gates -description: 'cert-manager contributing guide: Implementing feature gates' ---- - -As of v1 release cert-manager is considered stable. We aim to follow Kubernetes API compatibility policy when making API changes, see [API compatibility](../installation/api-compatibility.md) to avoid breaking users' existing cert-manager installations. -This means that as developers we are somewhat limited in regards to changing existing behavior, i.e renaming or removing API elements or changing their behavior. - -New functionality that is not yet stable[^1] can still be added, but it needs to be placed behind a feature gate. - -## Feature gated API fields - -Feature gated API fields are implemented using `--feature-gates` flags of cert-manager [webhook](../cli/webhook.md) and [controller](../cli/controller.md). - -A feature gated API field is always visible to the user (i.e when running `kubectl explain `), but is only functional if the relevant feature is explicitly enabled via feature flags for both the webhook and controller. - -If a user attempts to apply a resource with the feature gated field set to a non-nil value, but the feature gate is not enabled, the resource will get rejected by the webhook validation. -This mechanism differs from [the one that Kubernetes uses for feature gated API field implementation](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md#new-field-in-existing-api-version) where the field will be simply set to nil if the feature gate is disabled. We chose to use webhook validation instead to make debugging easier for users who are attempting to use the feature gated field, but have forgotten to enable the feature gate. - - -### Implementation - -- Implement the new field and document that it is feature gated and in order to use it the controller and webhook feature gates need enabling -- Add a new [webhook feature gate](https://github.com/cert-manager/cert-manager/blob/3a055cc2f56c1c2874807af4a8f84d0a1c46ccb4/internal/webhook/feature/features.go#L25-L39) for the field -- Update webhook validation checks for the relevant resource kind to ensure that if the feature gated field is set, but the webhook feature gate is not enabled, the resource gets rejected -- Add a new [controller feature gate](https://github.com/cert-manager/cert-manager/blob/2417132b3cd017b5f0974006e03c2b8a540efe3f/internal/controller/feature/features.go#L26-L54) for the field -- Ensure that any control loops that use the feature, check that the feature gate is actually enabled. (This is required to cover edge cases such as if the webhook runs a version of cert-manager where the feature is in GA whereas controller runs an older version where the feature is still in experimental state) -- Ensure that the feature gate is added to cert-manager installation scripts for CI and local tests in [make](https://github.com/cert-manager/cert-manager/blob/134398e939bb2b1401697eaf589405ad469cd609/make/e2e-setup.mk#L165) and [bazel](https://github.com/cert-manager/cert-manager/blob/fd747b42b9ab4b6409b61b7946e8dc14d532e950/devel/addon/certmanager/install.sh#L26) scripts -- Default cert-manger e2e CI tests run with all feature gates for all components enabled. There is an additional optional e2e test that runs with all feature gates disabled. You can trigger that for your PR with `/test pull-cert-manager-e2e-feature-gates-disabled` to verify that all works as expected both with and without the new feature gate. - -### Potential issues - -- The person deploying cert-manager has to remember to set two cert-manager feature gates, one of the webhook one on the controller for the feature to function. Forgetting to set one of them might result in unexpected behavior - -- A user must remember to remove the alpha fields from their manifests when disabling a previously enabled API feature. Failing to do so might result in unexpected behavior- for example forgetting to remove feature gated field from a `Certificate` resource might result in failed renewals at some later point when cert-manager's controller will attempt to update the `Certificate` spec, but the webhook will reject the update due to the feature gated field being set. - -### References - -- cert-manager's [API compatibility promise](../installation/api-compatibility.md) - -- An example implementation of an alpha field is [`AdditionalOutputFormats` field on `Certificate` spec](https://github.com/cert-manager/cert-manager/blob/dbad3d98f3d7d85cadb4bd2c2493faf8b666b313/internal/apis/certmanager/types_certificate.go#L169-L174) - -- [Kubernetes definition of feature stages](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages) - -- Kubernetes [API change design](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md) - -[^1]: For example, functionality that might change in the future in response to user feedback diff --git a/content/v1.13-docs/contributing/google-season-of-docs/2022/README.md b/content/v1.13-docs/contributing/google-season-of-docs/2022/README.md deleted file mode 100644 index 587036e7e1..0000000000 --- a/content/v1.13-docs/contributing/google-season-of-docs/2022/README.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Google Season of Docs 2022 -description: Google season of docs 2022 proposal ---- - -We registered our interest to participate in Google Season of Docs 2022! - -There's one project proposal: - -[Improve the Navigation and Structure of the cert-manager Website](./improve-navigation-and-structure.md) diff --git a/content/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md b/content/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md deleted file mode 100644 index 496ef1be33..0000000000 --- a/content/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md +++ /dev/null @@ -1,269 +0,0 @@ ---- -title: Improve the Navigation and Structure of the cert-manager Website -description: Google season of docs 2022 proposal ---- - -## Project Updates - -### 7 Sept 2022: The Webhook Debugging Guide - -friction log for task 3, before -friction log for task 3, after - -At the start of the Google Season of Docs program, we built friction logs for -common user tasks, such as debugging the error "connect: connection refused". -The friction log for this task, visible in the [GSoD work -document](https://docs.google.com/document/d/1O-MFWwtpOcNlrRzsiBvrpGHC10EXnvw0XK37M2nEjzg/edit#bookmark=id.cu9ss8s7yl46), -was to serve as a reference point to see whether the improvements we aimed to -bring would have an impact or not. - -The friction log showed a consistent pattern: the user searches the error on -Google, is confused by GitHub issues that don't have any solutions, then clicks -the second link in the Google results, without much luck. We realized that one -improvement we could make was to add a link to the FAQ page "Troubleshooting -Problems with the Webhook". We found two problems with this FAQ page: - -1. It could not be found by anyone because the error messages were not listed in - the page, meaning that Google would not show the page in the search results. -2. Many error messages were not listed in the page. - -We set ourselves to rewrite this page with the goal of making it error-focused, -meaning that the user would just be able to look for their particular error and -start debugging it. We called it "The Definitive Debugging Guide for the -cert-manager Webhook Pod", and it can be found -[here](../../../troubleshooting/webhook.md). - -### 12 Aug 2022: Improved the layout of the navigation menu - -On displays `>=1280px` the left-hand menu was too narrow to display the nested menu items clearly, -On smaller displays the [responsive CSS](https://tailwindcss.com/docs/responsive-design) actually made the menu larger. -So we've widened it by 1 column on displays `>=1280px` and reduced the width of the content by 1 column to compensate. -This makes the menu much easier to read on laptop and desktop computer screens. - -We fixed an inconsistency in the vertical spacing between menu items with sub-menus and those without. - -And finally, we moved the version selector to the bottom of the side-bar to avoid distracting the reader. - -### 3 August 2022: The cert-manager.io Documentation Survey is now closed - -Thank you to everyone who participated in our documentation survey. -We will use the results to prioritize sections of the website for restructuring and rewriting. -Before the conclusion of this Season-of-Docs we will select a random winner from among the responses and contact you about your prize. - -### 18 July 2022: The cert-manager.io Documentation Survey - -Screenshot 2022-07-18 at 14-35-48 cert-manager documentation survey - -We have created a short survey, to help us identify what are the top-priorities for the cert-manager.io documentation. - -1. We want identify the most useful documentation, so that we don't go and change things that are already working well. -2. We want to know which documentation is not useful, so that we can make improvements. -3. We'd like to hear from new and experienced users about how and how often you use the documentation. -4. And we'd like to know where else you find good information about cert-manager, outside of the cert-manager.io website, -so that we can try and incorporate some of those sources. - -We've added a link to the survey to the banner at the top of this site -and we will also be sharing the link in our Slack channels and mailing lists. - -[Please take 10 minutes to fill in the survey](https://docs.google.com/forms/d/e/1FAIpQLSeqfRkd86_N0L7VOW_ImCT0iyUabhczdiDk2dQDLp55V8kqvw/viewform). - -
              - -### 15 July 2022: New "Getting Started" pages - - - - -We have been auditing the existing documentation to identify some key tasks that our users and potential new users need to carry out. -We have created "friction logs" for some of these tasks. -What this means is that we imagine ourselves in the place of the user and ask, for example, - -> How can I get a Let's Encrypt certificate for my server in Kubernetes? - -So we searched Google and DuckDuckGo for "Let's Encrypt Kubernetes" and to our surprise, cert-manager.io does not feature among the top search results. - -Among the results are some excellent third-party tutorials and videos about using cert-manager to create Let's Encrypt certificates, -and we are grateful to the authors for taking the time to write such detailed content. -But inevitably, some of these refer to much older versions of cert-manager and Kubernetes. -So we have decided to write some official guides, for the cert-manager.io website which demonstrate how to quickly install cert-manager and configure it for Let's Encrypt. -We hope that in time these will be indexed by the search engines and that they will reach the top of the search results for "Let's Encrypt Kubernetes". -The advantages will be that users and potential users will find up-to-date information, -and the cert-manager.io maintainers will receive fewer support requests from new users who are attempting this task. - -Go and read the new [Getting Started Guide for GKE Users](../../../getting-started) and tell us what you think. - -
              - -### 5 May 2022: Announcing Mehak Saeed as Technical Writer - -We are delighted to announce that [Mehak Saeed](https://www.linkedin.com/in/mehak-saeed-29121a12a) will be the technical writer working on this project. -We were extremely impressed with Mehak's presentation during her interview and impressed with her detailed preparations and planning. -We look forward to working with her. - -Thank you to all the other technical writers who applied for this project. - -### 14 April 2022: Project Accepted - -This project was [accepted on 14 April 2022](https://developers.google.com/season-of-docs/docs/participants). - -### 24 March 2022: Project Registered - -We have [registered our interest to participate in Google Season of Docs 2022](https://github.com/google/season-of-docs/pull/483), -and have submitted a single project proposal detailed in the remaining of this -page. - -You have until 27 April 2022 18:00 UTC to apply for the technical writer role. - -We will be sharing the name of the selected candidate on Wed 4 May 2022 at -15:00 London Time (14:00 UTC) on Slack in the channel `#cert-manager-dev`. - -To apply as a technical writer, please let us know by one of the two ways -below: - -- e-mail us at `cert-manager-maintainers@googlegroups.com` with the prefix - `GSoD2022:` in the e-mail subject. -- or open an issue on - [cert-manager/website](https://github.com/cert-manager/website) with the - prefix `GSoD2022:` in the issue title. - -You can join our open standup (every day at 10:30 UK time), and join the -Kubernetes Slack channel `#cert-manager-dev` to know more about this project -proposal. - -## About cert-manager - -cert-manager (current version 1.8.0, first release in October 2017) is an Apache-2.0 licensed Kubernetes add-on to automate the management and issuance of TLS certificates. - -Our typical contributors are Go developers from around the world with experience of the Kubernetes ecosystem with experience contributing to core Kubernetes components and Kubernetes operators. - -Our users are often developers and system administrators who are trying to automate the rotation of TLS certificates for applications running in their Kubernetes clusters. - -Our largest users have cert-manager installed on multiple Kubernetes clusters and managing many thousands of TLS certificates. - -## Project Overview - -Right now the content is not designed with our target audiences in mind. -For example a new user will not easily find a guide explaining how to install cert-manager on AWS and configure it for Let’s Encrypt. -Nor will a Cluster Administrator easily find information about how to optimize cert-manager for a large cluster with many Certificates. -The information exists but is spread across multiple pages and is often not at the obvious page. - -As a visual example, a user looking for a guide on how the Certificate resource can be used may feel helpless when realizing that the "Certificate" page exists twice: once under the "Usage" section, and once under the "Concepts" section. - -![Screenshot of the cert-manager.io website with Usage and Concepts visible in the menu](/images/google-season-of-docs-2022-improve-navigation-and-structure.png) - -(NB: This screenshot is from our old site design but the text and layout are broadly the same) - -We would like a technical writer: - -1. to help us identify our target audiences, and -2. to identify the key tasks of each of these audiences, and -3. re-structure the cert-manager.io website with this in mind. - -For example, we have discussed the following audiences and tasks: Beginner, Cluster Administrator, User, Integrator, New Contributor -and each of these people will be interested in a different set of tasks. -We would like them to quickly and easily find the information they need. - -By making it easier for each group to find the information they need we aim to reduce the number of support queries. - -## Audiences - -### New User - -Has never used cert-manager and may never have used Kubernetes. -Wants to find out what cert-manager can offer. -May have heard about cert-manager in another tutorial. -May want to know what are the alternatives to cert-manager and the trade offs. -Needs to install cert-manager quickly so that they evaluate it on their laptop. -Needs to learn basic configuration of cert-manager. -Needs to understand what are the next steps. - -### Ongoing User - -A programmer who wants to deploy a TLS protected APP. -Knows that cert-manager has been installed by their cluster administrator. -Has an existing Issuer or ClusterIssuer. -Needs to know how to create a Certificate which is appropriate for their application. E.g. -* Create a certificate for their PostgreSQL database -* Create an certificate for their Ingress / Gateway -Needs to know how to debug why their certificate hasn’t renewed -Needs to understand the error messages on cert-manager Certificates and Certificate requests -Needs to know which errors they can fix and which errors require assistance from their cluster administrator. - -### Cluster Administrator - -Knows Kubernetes. -Has a long running cert-manager installation. -Wants to know how to configure it and upgrade it for optimum performance. -Wants to optimize for large numbers of certificates. -Wants to upgrade from older versions. -Wants to monitor cert-manager performance -Wants to set up alerts to notify them when cert-manager goes wrong. -May want to configure cert-manager for multiple cloud providers. -May want to get cert-manager working with some other cluster scoped system like Istio or Knative. - -### Integrator - -May want to allow cert-manager users to make use of a custom Certificate service. -May want to integrate cert-manager with a DNS API for ACME DNS01. -May want to depend on cert-manager for managing TLS certificates for a higher level system. -Needs to learn how to write plugins / extensions for cert-manager. -Needs links to state-of-the-art examples of plugins and extensions. - -### New Contributor - -Wants to report a bug in cert-manager. -Wants to fix a bug in cert-manager. -Wants to suggest a feature for cert-manager. -Wants to implement a feature for cert-manager. -Needs to learn how to navigate the cert-manager code. -Learn cert-manager coding standards and house style. -Needs to know how to run the tests for cert-manager. - -## Scope - -The scope of this project is as follows: - -1. Identify and describe three target audiences. -2. Identify three key top tasks for each of these audiences. -3. Audit the existing documentation and create a friction log of the current documentation. -4. Using the friction log as a baseline, re-organize the documentation to minimize friction for three top tasks. -6. Incorporate feedback from documentation testers (volunteers in the project) and the wider cert-manager community. -7. Work with the cert-manager team to publish the documentation on cert-manager.io. -8. Create documentation for website contributors explaining how we structure our content around audiences and tasks. - -## Measuring success - -After the technical writer has helped us identify the 3 key tasks for each audience -we will measure a baseline number of clicks required to achieve the task and we will aim to minimize the number of clicks for each task. - -## Timeline - -| Dates | Action Items | -|-------------|--------------------------------------------------| -| May | Orientation | -| May / June | Identify audiences and tasks | -| May / June | Audit and friction log | -| June | Restructuring tasks | -| June / July | Incorporating feedback | -| June / July | Publish to cert-manager.io | -| July | Finish writing guidance for website contributors | -| July | Project Completion | - -## Budget - -| Budget item | Amount ($) | Running Total ($) | Notes | -|-------------------------------------------------------------------------------|-------------|-------------------|---------------------------------| -| Technical writer audit and restructuring of the cert-manager.io documentation | 12,000 | 12,000 | | -| Volunteer stipends | 1,500 | 13,500 | 3 volunteer stipends x 500 each | -| TOTAL | | 13,500 | | - -Regarding the amount of $12,000, we assume that it will be enough to fund one experienced technical writer -part-time (for example, they could work half day from Tuesday to Friday, for a total of 24 days, for 3 months -at a daily rate of $500). - -We will give the "volunteer stipend" to contributors who can show they have one PR within the project -time frame (from 1st May to 30th July) in which a re-write of one page or a set of pages. Before -starting the rewriting, the volunteer will suggest which page they wish to work on either on Slack -(Kubernetes Slack, channel #cert-manager-dev), or in an issue on GitHub, and make sure by asking the -team whether it makes sense to rework this page. As long as at least one positive reaction, the -volunteer can start working. For the stipend to be validated, the PR needs to be reviewed and merged. diff --git a/content/v1.13-docs/contributing/google-season-of-docs/README.md b/content/v1.13-docs/contributing/google-season-of-docs/README.md deleted file mode 100644 index cb43f69aa8..0000000000 --- a/content/v1.13-docs/contributing/google-season-of-docs/README.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Google Season of Docs -description: cert-manager and Google Season of Docs ---- - -The cert-manager project participated in Google Season of Docs 2022 - -If you're interested in what happened, you can check out our [2022 proposals!](./2022/README.md). diff --git a/content/v1.13-docs/contributing/importing.md b/content/v1.13-docs/contributing/importing.md deleted file mode 100644 index 2c39a247e3..0000000000 --- a/content/v1.13-docs/contributing/importing.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Importing cert-manager in Go -description: 'cert-manager contributing guide: Importing cert-manager' ---- - -cert-manager is written in Go, and uses Go modules. You _can_ import it as a Go module, and in some cases -that's fine or even encouraged, but as a rule we generally recommend against importing cert-manager. - -Generally speaking, except for the cases listed below under [When You Might Import cert-manager](#when-you-might-import-cert-manager), -code in the cert-manager repository is *not* covered under any Go module compatibility guarantee. We can and will make breaking -changes, even in publicly exported Go code and even in a minor or patch release of cert-manager. We have made breaking changes like -this in the past. - -Note that this doesn't affect _running_ cert-manager. Our commitment on compatibility is to not break the runtime -functionality of cert-manager, and we take that seriously. - -If you're certain that you *do* need to import cert-manager as a module, see [Module Import Paths](#module-import-paths) -below for a note on how to do that. - -## When You Might Import cert-manager - -You might need to import cert-manager if you're writing Go code which: - -- uses cert-manager custom resources, so you want to import something under `pkg/apis` -- implements an external DNS solver webhook, as in the [webhook-example](https://github.com/cert-manager/webhook-example) -- implements an external issuer, as in the [sample-external-issuer](https://github.com/cert-manager/sample-external-issuer) - -If you think you really need to import other parts of the code, please do reach out and [talk to us](./README.md#slack) so we're -aware of this need! We'll always try to avoid breakage where we can. - -## Module Import Paths - -For all supported versions of cert-manager, the module import path is `github.com/cert-manager/cert-manager`. - -Historically, the cert-manager repository was created on GitHub as `https://github.com/jetstack/cert-manager`, and was later -migrated to `https://github.com/cert-manager/cert-manager`. - -This means that the Go module import path you need may be different if you're trying to use an older version of cert-manager. - -For cert-manager 1.8 and later, use the new path listed above. - -For cert-manager versions older than 1.8 use the old path: `github.com/jetstack/cert-manager` diff --git a/content/v1.13-docs/contributing/kind.md b/content/v1.13-docs/contributing/kind.md deleted file mode 100644 index a6fc15c14a..0000000000 --- a/content/v1.13-docs/contributing/kind.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Developing with Kind -description: 'cert-manager contributing guide: Using Kind' ---- - -[Kind](https://kind.sigs.k8s.io/) allows you to provision Kubernetes clusters locally using nested Docker containers, -with no requirement for virtual machines. - -These clusters are quick to create and destroy, and are useful for simple testing for -development. cert-manager also uses kind clusters in its [end-to-end tests](./e2e.md). - -## Using Kind Locally - -You should be able to make use of cert-manager's end-to-end test setup logic to create a local Kind cluster for -development. As such, if you want a local cluster you might want to follow some of the details in the -[end-to-end test documentation](./e2e.md). - -If, though, you just want to get a cluster up and running with your local changes to cert-manager running inside -`kind`, try the following: - -```console -make e2e-setup-kind e2e-setup-certmanager -``` - -Or, if you need a specific version of Kubernetes: - -```console -make K8S_VERSION=1.xx e2e-setup-kind e2e-setup-certmanager -``` - -That should leave you with a working cluster which you can interact with using `kubectl`! diff --git a/content/v1.13-docs/contributing/policy.md b/content/v1.13-docs/contributing/policy.md deleted file mode 100644 index d29f34e41f..0000000000 --- a/content/v1.13-docs/contributing/policy.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: Feature Policy -description: 'cert-manager contributing guide: Contribution Policy' ---- - -We love to receive both feature requests and PRs which add to and improve cert-manager; the community is at the heart of what we do! - -If you're thinking of adding a feature, we recommend you read this doc to maximize the chances of your contribution getting the attention it deserves and hopefully to get it merged quickly! - -We recommend creating an issue first for it to be discussed with the cert-manager maintainers. Another possibility is bringing it up in a community meeting for an open discussion on the implementation. - -## Feature Sizing: Getting Your Change Accepted - -We evaluate new features and PRs based on their size and their significance; either they're small or large. - -### Smaller Features - -Many contributions are small. That usually - but not always - means that implementing them won't require many lines of code to be added or changed, and in any case they should be easy -for maintainers to review. A PR being small is a good thing; if you can down-scope your feature to make it smaller, we won't complain! - -If you believe your feature is small, please feel free to just raise a PR and optionally also post a link to your PR in the [cert-manager-dev slack channel](./README.md#slack). Usually a sufficiently small PR can be merged without too much ceremony. If we think it's actually a larger piece of work, we'll let you know. - -### Larger Features - -If you're not sure whether your PR is small, or if you know it's bigger, you'll want to speak to us first before raising a PR. This -will help to ensure that your PR is something we're likely to merge to avoid wasting your time. It'll also make it easier -for us to do the design process. - -#### Design Documents - -Larger feature development should normally start with a design discussion. To get that started, you would raise a PR with a design document against [cert-manager/cert-manager/design](https://github.com/cert-manager/cert-manager/tree/master/design). This allows us to discuss the proposed functionality before starting the work to implement it and serves as a way to document the decisions and reasoning behind them. Ideally, a good design document should allow for faster and more consistent feature development and implementation process by providing a single place where all potential concerns and questions are answered. - -We have a [design template](https://github.com/cert-manager/cert-manager/blob/master/design/template.md) that outlines the structure of the document. -(This is a simplified version of [Kubernetes enhancements KEP template](https://github.com/kubernetes/enhancements/tree/master/keps/NNNN-kep-template)). -Do reach out if you need help with the design. - -Part of the process of discussing a design document may also include a video call with you included! That helps us to plan how a feature should -be implemented and approached. It'll be pretty informal and casual; we just want to make sure we're all on the same page. This call might be part -of a biweekly meeting. - -#### Making Progress with Larger Features - -Larger features with a design document are much more likely to be accepted, and in turn we're much more likely to commit a single -named cert-manager maintainer to the effort to help the PR to be successful. That maintainer might not be able to answer all your -questions, but they should certainly be able to point you in the right direction. - -To get in touch to discuss a feature, please reach out on the [cert-manager-dev slack channel](./README.md#slack), or join a [cert-manager public meeting](./README.md#meetings) to talk about your proposal. - -If you have an open PR with a design document (or have some questions about how to proceed with a design), you should absolutely feel free to add the PR with your design or a link to the relevant GitHub issue to the [meeting notes](https://docs.google.com/document/d/1Tc5t6ylY9dhXAan1OjOoldeaoys1Yh4Ir710ATfBa5U/edit) for our next biweekly meeting -and join in so we're sure to discuss it and so you can contribute to the discussion! - -#### Large Feature Lifecycle - -1. Informally ask about the feature in slack or a public meeting -2. Create a PR with a lightweight design document using the [design template](https://github.com/cert-manager/cert-manager/tree/master/design/template.md), for discussion -3. Design doc PR gets reviewed - possibly includes meeting or discussion in a biweekly meeting -4. Implement your feature, helped and reviewed by a named cert-manager maintainer - -## Feature Requests We'll Likely Reject - -In some cases, people will request features which we've previously rejected or which for some reason we have to reject. - -It's nothing personal; sometimes we have to make tough choices and especially when it comes to security and maintainability we have to reject certain -proposals. If your feature request is listed below, there's a high chance we'll have to reject it. - -That said, if you think we've made a mistake and that we should reconsider, we're open to chatting - consider joining our [biweekly meetings](./README.md#meetings) to discuss it with us! - -### Vendoring Kubernetes related APIs outside of the `k8s.io/` namespace - -Vendoring project APIs that also vendor `k8s.io/apimachinery`, such as OpenShift, Contour, or Velero, is not recommend because the Kubernetes dependency is likely to conflict with cert-manager's instance. -It could also cause a conflict with different Kubernetes client versions being used. - -If this is needed it is suggested to use a "dynamic client" that converts the objects into internal structures copied into the cert-manager codebase. - -### Additional configuration options for the Helm chart - -cert-manager's Helm chart is intended to allow to create a standard, best practices cert-manager installation with basic configuration options, such as being able to provide flags to cert-manager components, label resources etc. -We do not aim to include every possible configuration option for resources that the chart creates to avoid maintenance burden and because we do not have automated testing for all chart configuration options. Therefore we are likely to not accept PRs that add advanced or niche configuration options to Helm charts- we recommend that users who require that configuration use another mechanism such as [Helm's post-install hooks](https://helm.sh/docs/topics/charts_hooks/). - -### Helm + CRDs - -Helm suggests that CRDs be included in a `crds/` subdirectory of a chart, with the `crd-install` annotation included. This has the unfortunate side effect that CRDs are not upgraded if changed in a later release. - -CRDs being upgraded without being removed and re-installed is essential for cert-manager to move forward. - -This was previously discussed [in the Helm community](https://github.com/helm/helm/issues/5871). - -cert-manager works around this limitation by shipping CRDs in the templates. - -### Helm Subchart capabilities - -cert-manager now has the capability to be [installed as a subchart](../installation/helm.md#installing-cert-manager-as-subchart). - -But you need to be careful when adding it to your umbrella chart. - -This is because the cert-manager installation creates cluster scoped resources like admission webhooks and custom resource definitions. cert-manager should be seen as part of your cluster and should be treated as such for being installed. An apt comparison -to other Kubernetes components would be a LoadBalancer controller or a PV provisioner. - -It is your responsibility to ensure that cert-manager is only installed once in your cluster. -This can be managed via the `condition` parameter of the dependency in your `Chart.yaml`, which allows users to disable the installation of a subchart. The condition parameter must be added when using cert-manager as a subchart to allow users to disable your dependency. - -```yaml -apiVersion: v2 -name: example_chart -description: A Helm chart with cert-manager as subchart -type: application -version: 0.1.0 -appVersion: "0.1.0" -dependencies: - - name: cert-manager - version: v1.8.0 - repository: https://charts.jetstack.io - alias: cert-manager - condition: cert-manager.enabled -``` - -### Secret injection or copying - -cert-manager deals with very sensitive information (all TLS certificates for your services) and has cluster-level access to secret resources. As such, when designing features we need to consider all of the ways these secrets might be abused to escalate privilege. - -Secret data is meant to be securely stored in `Secret` resources and have narrow scoped access privileges for unauthorized users. Because of this, we won't usually add any functionality that allows this data to be copied/injected into any resource -other than a Kubernetes `Secret`. - -#### cainjector - -The cainjector component is a special exception to this rule as it deals in non-sensitive information (CAs, not cert/key pairs). This component is able to inject the `ca.crt` file into predefined fields on `ValidatingWebhookConfiguration`, `MutatingWebhookConfiguration`, and `CustomResourceDefinition` resources from Certificate resources. - -These 3 components are already scoped only for privileged users, and will already give you cluster scoped access to resources. - -If you’re designing a resource that needs a CA Certificate or TLS key pair it is strongly recommended to use a reference to a secret instead of embedding it in a resource. - -### Cross namespace resources - -Namespace boundaries in Kubernetes provide a barrier for access scopes. Apps or users can be limited to only access resources in a certain namespace. - -cert-manager is a controller that operates on cluster wide resources however, and while it may seem interesting to allow access to copy or write certificate data from one namespace to the other, this can cause a bypass of the -namespace security model for all users, which is usually not intended and can be a major a security issue. - -We don't support this behavior; if you believe you need it, and it's intended for your use case then there are other Kubernetes controllers that can do this, although we'd suggest extreme caution. - -### Sign certificates using the Kubernetes CA - -Kubernetes has a Certificate Signing Requests API, and a `kubectl certificates` command which allows you to approve certificate signing requests and have them signed by the certificate authority (CA) of the Kubernetes cluster. This -CA is generally used for your nodes. - -This API and CLI have occasionally been misused to sign certificates for use by pods outside of the control plane; we believe this is a mistake. - -For the security of the Kubernetes cluster it's important to limit access to the Kubernetes certificate authority; such certificates increase the attack surface for the Kubernetes API server since this CA signs certificates for -authorization against the API server. If cert-manager used this cert, it could allow any user with permission to create cert-manager resources to elevate privileges by signing certificates which are trusted for API access. - -[See our FAQ](../faq/README.md#kubernetes-has-a-builtin-certificatesigningrequest-api-why-not-use-that) for more details on this. - -### Integrations with third party infrastructure providers - -We try to not include in core cert-manager new functionality that involves calling third party APIs that we don't have infrastructure to test (or that the maintainers don't have the skills to work with). - -Instead we try to build interfaces such as [external DNS webhook solver](../configuration/acme/dns01/webhook.md) that can be implemented to use cert-manager with a particular third party implementation. -We believe that this is a more sustainable approach as that way folks who have knowledge and skills to work with particular infrastructure can own a project that interacts with it and it lets us avoid merging potentially untested code to core cert-manager. -An example of a PR that might be rejected would be adding a new external DNS solver kind, see https://github.com/cert-manager/cert-manager/pull/1088 diff --git a/content/v1.13-docs/contributing/release-process.md b/content/v1.13-docs/contributing/release-process.md deleted file mode 100644 index 30d273485a..0000000000 --- a/content/v1.13-docs/contributing/release-process.md +++ /dev/null @@ -1,735 +0,0 @@ ---- -title: Release Process -description: 'cert-manager contributing: Release process' ---- - -This document aims to outline the process that should be followed for -cutting a new release of cert-manager. If you would like to know more about -current releases and the timeline for future releases, take a look at the -[Supported Releases](../installation/supported-releases.md) page. - -## Prerequisites - -⛔️ Do not proceed with the release process if you do not meet all of the -following conditions: - -1. The relevant [testgrid dashboard](https://testgrid.k8s.io/cert-manager) should not be failing for the release you're trying to perform. -2. The release process **takes about 40 minutes**. You must have time to complete all the steps. -3. You currently need to be at Jetstack to get the required GitHub and GCP - permissions. (we'd like contributors outside Jetstack to be able to get - access; if that's of interest to you, please let us know). -4. You need to have the GitHub `admin` permission on the cert-manager project. - To check that you have the `admin` role, run: - - ```bash - brew install gh - gh auth login - gh api /repos/cert-manager/cert-manager/collaborators/$(gh api /user | jq -r .login)/permission | jq .permission - ``` - - If your permission is `admin`, then you are good to go. To request the - `admin` permission on the cert-manager project, [open a - PR](https://github.com/jetstack/platform-board/pulls/new) with a link to - here. - -5. You need to be added as an "Editor" to the GCP project - [cert-manager-release](https://console.cloud.google.com/?project=cert-manager-release). - To check if you do have access, try opening [the Cloud Build - page](https://console.cloud.google.com/cloud-build?project=cert-manager-release). - To get the "Editor" permission on the GCP project, open a PR with your name - added to the maintainers list in - [`cert_manager_release.tf`](https://github.com/jetstack/terraform-jetstack/blob/master/cert_manager_release.tf) - - ```diff - --- a/cert_manager_release.tf - +++ b/cert_manager_release.tf - @@ -17,6 +17,7 @@ locals { - var.personal_email["..."], - var.personal_email["..."], - var.personal_email["..."], - + var.personal_email["mael-valais"], - ]) - } - ``` - - You may use the following PR description: - - ```markdown - Title: Access to the cert-manager-release GCP project - - Hi. As stated in "Prerequisites" on the [release-process][1] page, - I need access to the [cert-manager-release][2] project on GCP in - order to perform the release process. Thanks! - - [1]: https://cert-manager.io/docs/contributing/release-process/#prerequisites - [2]: https://console.cloud.google.com/?project=cert-manager-release - ``` - -This guide applies for versions of cert-manager released using `make`, which should be every version from cert-manager 1.8 and later. - -**If you need to release a version of cert-manager 1.7 or earlier** see [older releases](#older-releases). - -First, ensure that you have all the tools required to perform a cert-manager release: - -1. Install the [`release-notes`](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md) CLI: - - ```bash - go install k8s.io/release/cmd/release-notes@v0.13.0 - ``` - -2. Install our [`cmrel`](https://github.com/cert-manager/release) CLI: - - ```bash - go install github.com/cert-manager/release/cmd/cmrel@latest - ``` - -3. Clone the `cert-manager/release` repo: - - ```bash - # Don't clone it from inside the cert-manager repo folder. - git clone https://github.com/cert-manager/release - cd release - ``` - -4. Install the [`gcloud`](https://cloud.google.com/sdk/) CLI. -5. [Login](https://cloud.google.com/sdk/docs/authorizing#running_gcloud_auth_login) - to `gcloud`: - - ```bash - gcloud auth application-default login - ``` - -6. Make sure `gcloud` points to the cert-manager-release project: - - ```bash - gcloud config set project cert-manager-release - export CLOUDSDK_CORE_PROJECT=cert-manager-release # this is used by cmrel - ``` - -7. Get a GitHub access token [here](https://github.com/settings/tokens) - with no scope ticked. It is used only by the `release-notes` CLI to - avoid API rate limiting since it will go through all the PRs one by one. - -## Minor releases - -A minor release is a backwards-compatible 'feature' release. It can contain new -features and bug fixes. - -### Release schedule - -We aim to cut a new minor release once per month. The rough goals for each -release are outlined as part of a GitHub milestone. We cut a release even if -some of these goals are missed, in order to keep up release velocity. - -### Process for releasing a version - -
              -🔰 Please click on the **Edit this page** button on the top-right corner of this -page if a step is missing or if it is outdated. -
              - -1. Remind yourself of our release terminology by looking at the following table. - This will allow you to know which steps to skip by looking the header of the - step, e.g., **(final release only)** means that this step must only be - performed when doing a final release. - - | Type of release | Example of git tag | - |------------------------------------|--------------------| - | initial alpha release | `v1.3.0-alpha.0` | - | subsequent alpha release | `v1.3.0-alpha.1` | - | initial beta release | `v1.3.0-beta.0` | - | subsequent beta release | `v1.3.0-beta.1` | - | final release | `v1.3.0` | - | (optional) patch pre-release[^1] | `v1.3.1-beta.0` | - | patch release (or "point release") | `v1.3.1` | - - [^1]: One or more "patch pre-releases" may be created to allow voluntary community testing of a bug fix or security fix before the fix is made generally available. The suffix `-beta` must be used for patch pre-releases. - -2. Set the 4 environment variables by copying the following snippet in your - shell table: - - ```bash - export RELEASE_VERSION="v1.3.0-alpha.0" - export START_TAG="v1.2.0" - export END_REV="release-1.3" - export BRANCH="release-1.3" - ``` - - > **Note:** To help you fill in the correct values, use the following - > examples: - > - > | Variable | Example 1 | Example 2 | Example 2 | Example 3 | Example 4 | - > | ----------------- | ---------------- | ---------------- | ---------------- | ------------- | ------------- | - > | | initial alpha | subsequent alpha | beta release | final release | patch release | - > | `RELEASE_VERSION` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.3.0-beta.0` | `v1.3.0` | `v1.3.1` | - > | `START_TAG` | `v1.2.0` | `v1.3.0-alpha.0` | `v1.3.0-alpha.1` | `v1.2.0`\* | `v1.3.0` | - > | `END_REV` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | - > | `BRANCH` | `master` | `master` | `release-1.3` | `release-1.3` | `release-1.3` | - > - > \*Do not use a patch here (e.g., no `v1.2.3`). It must be `v1.2.0`: - > you must use the latest tag that belongs to the release branch you are - > releasing on; in the above example, the release branch is - > `release-1.3`, and the latest tag on that branch is `v1.2.0`. - - > **Note:** The 4 variables are described in [the README of the - `release-notes` - tool](https://github.com/kubernetes/release/blob/master/cmd/release-notes/README.md#options). - For your convenience, the following table summarizes what you need to know: - > - > | Variable | Description | - > | ----------------- | --------------------------------------- | - > | `RELEASE_VERSION` | The git tag | - > | `START_TAG`\* | The git tag of the "previous"\* release | - > | `END_REV` | Name of your release branch (inclusive) | - > | `BRANCH` | Name of your release branch | - -3. **(final release only)** Prepare the Website "Upgrade Notes" PR. - - Make sure that a PR with the new upgrade - document is ready to be merged on - [cert-manager/website](https://github.com/cert-manager/website). See for - example, see - [upgrading-1.0-1.1](https://cert-manager.io/docs/installation/upgrading/upgrading-1.0-1.1/). - -4. **(final + patch releases)** Prepare the Website "Release Notes" PR. - - **⚠️ This step can be done ahead of time.** - - The steps below need to happen using `master` (**final release**) or - `release-1.x` (**patch release**). The PR will be merged after the release. - - 1. Go to the Generate `release-notes.md` using the instructions further below - (Ctrl+F and look for `github-release-description.md`). - 2. Remove the "Dependencies" section. - 3. Edit any `release-note` block in the PR description that doesn't follow - the [release-note guidelines](../contributing/contributing-flow.md#release-note-guidelines) - and copy the same change into `release-notes.md` (or re-generate the - file). - 4. Add the section "Major themes" and "Community" by taking example on the - previous release note pages. - 5. Replace the GitHub issue numbers and GitHub handles (e.g., `#1234` or - `@maelvls`) with actual links using the following command: - - ```bash - sed github-release-description.md \ - -e 's$#([0-9]+)$[#\1](https://github.com/cert-manager/cert-manager/pull/\1)$g' \ - -e 's$@(\w+)$[@\1](https://github.com/\1)$g' \ - -E \ - -i - ``` - - 6. Move `release-notes.md` to the website repo: - - ```bash - # From the cert-manager repo. - mv release-notes.md ../website/content/docs/release-notes-1.X.md - ``` - - 7. Add an entry to `content/docs/manifest.json`: - - ```diff - { - "title": "Release Notes", - "routes": [ - + { - + "title": "v1.12", - + "path": "/docs/release-notes/release-notes-1.12.md" - + }, - ``` - - 8. Add a line to the file `content/docs/release-notes/README.md`. - -5. **(final + patch release)** Prepare the Website "Bump Versions" PR. - - **⚠️ This step can be done ahead of time.** - - In that PR: - - 1. (**final release**) Update the section "Supported releases" in the - [supported-releases](../installation/supported-releases.md) page. - 2. (**final release**) Update the section "How we determine supported - Kubernetes versions" on the - [supported-releases](../installation/supported-releases.md) page. - 3. (**final release**) Bump the version that appears in - `scripts/gendocs/generate-new-import-path-docs`. For example: - - ```diff - -LATEST_VERSION="v1.11-docs" - +LATEST_VERSION="v1.12-docs" - - -genversionwithcli "release-1.11" "$LATEST_VERSION" - +genversionwithcli "release-1.12" "$LATEST_VERSION" - ``` - - 4. (**final + patch release**) Bump all versions present in installation - instructions. To find these versions: - - ```bash - find ./content/docs/installation -name '*.md' -not -path '*/upgrad**' -exec sed -i.bak 's/1.11../1.12.0/g' '{}' \; - rm -f **/*.bak - ``` - - To check that all mentions of that version are gone, run: - - ```bash - grep -R -n -F 'v1.11.' content/docs/installation - ``` - - 5. (**final release only**) Freeze the `docs/` folder by creating a copy , - removing the pages from that copy that don't make sense to be versioned, - and updating the `manifest.json` file: - - ```bash - cp -r content/docs content/v1.12-docs - rm -rf content/v1.12-docs/{installation/supported-releases,installation/upgrading,release-notes} - sed -i.bak 's|docs|v1.12-docs|g' content/v1.12-docs/manifest.json - cat content/v1.12-docs/manifest.json \ - | jq 'del(.. | select(.path? | select(.) | test(".*(installation/supported-releases.md|installation/upgrading|release-notes).*")))' \ - | jq 'del(.. | select(.routes? == []))' >/tmp/manifest \ - && mv /tmp/manifest content/v1.12-docs/manifest.json - ``` - - 6. (**final + patch releases**) Update the [API docs](https://cert-manager.io/docs/reference/api-docs/) and [CLI docs](https://cert-manager.io/docs/cli//): - - ```bash - # From the website repository, on the master branch. - ./scripts/gendocs/generate - ``` - -6. Check that the `origin` remote is correct. To do that, run the following - command and make sure it returns the upstream - `https://github.com/cert-manager/cert-manager.git`: - - ```bash - # Must be run from the cert-manager repo folder. - git remote -v | grep origin - ``` - - It should show: - - ```text - origin https://github.com/jetstack/cert-manager (fetch) - origin https://github.com/jetstack/cert-manager (push) - ``` - -7. Place yourself on the correct branch: - - - **(initial alpha and subsequent alpha)**: place yourself on the `master` - branch: - - ```bash - git checkout master - git pull origin master - ``` - - - **(initial beta only)** The release branch doesn't exist yet, so let's - create it and push it: - - ```bash - # Must be run from the cert-manager repo folder. - git checkout master - git pull origin master - git checkout -b release-1.12 master - git push origin release-1.12 - ``` - - **GitHub permissions**: `git push` will only work if you have the `admin` - GitHub permission on the cert-manager repo to create or push to the - branch, see [prerequisites](#prerequisites). If you do not have this - permission, you will have to open a PR to merge master into the release - branch), and wait for the PR checks to become green. - - - **(subsequent beta, patch release and final release)**: place yourself on - the release branch: - - ```bash - git checkout release-1.12 - git pull origin release-1.12 - ``` - - You don't need to fast-forward the branch because things have been merged - using `/cherry-pick release-1.0`. - - **Note about the code freeze:** - - The first beta starts a new "code freeze" period that lasts until the - final release. Just before the code freeze, we fast-forward everything - from master into the release branch. - - During the code freeze, we continue merging PRs into master as usual. - - We don't fast-forward master into the release branch for the second (and - subsequent) beta, and only `/cherry-pick release-1.0` the fixes that should be part - of the subsequent beta. - - We don't fast-forward for patch releases and final releases; instead, we - prepare these releases using the `/cherry-pick release-1.0` command. - - > Note about branch protection: The release branches are protected by [GitHub branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule), which is [configured automatically by Prow](https://github.com/cert-manager/testing/blob/500b990ad1278982b10d57bf8fbca383040d2fe8/config/config.yaml#L27-L36). - > This prevents anyone *accidentally* pushing changes directly to these branches, even repository administrators. - > If you need, for some reason, to fast forward the release branch, - > you should delete the branch protection for that release branch, using the [GitHub branch protection web UI](https://github.com/cert-manager/cert-manager/settings/branches). - > This is only a temporary change to allow you to update the branch. - > [Prow will re-apply the branch protection within 24 hours](https://docs.prow.k8s.io/docs/components/optional/branchprotector/#updating). - -8. Create the required tags for the new release locally and push it upstream (starting the cert-manager build): - - ```bash - echo $RELEASE_VERSION - git tag -m"$RELEASE_VERSION" $RELEASE_VERSION - # be sure to push the named tag explicitly; you don't want to push any other local tags! - git push origin $RELEASE_VERSION - ``` - - > **Note**: `git push` will only work if you have the `admin` GitHub - > permission on the cert-manager repo to create or push to the branch, see - > [prerequisites](#prerequisites). If you do not have this permission, you - > will have to open a PR to merge master into the release branch), and - > wait for the PR checks to become green. - - > **Note 2:** For recent versions of cert-manager, the tag being pushed will trigger a Google Cloud Build job to start, - > kicking off a build using the steps in `gcb/build_cert_manager.yaml`. Users with access to - > the cert-manager-release project on GCP should be able to view logs in [GCB build history](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). - -9. In this step, we make sure the Go module - `github.com/cert-manager/cert-manager/cmd/cmctl` can be imported by - third-parties. - - First, create a temporary branch. - - ```bash - # Must be run from the cert-manager repo folder. - git checkout -b "update-cmd/ctl/$RELEASE_VERSION" - ``` - - Second, update the `cmd/cmctl`'s `go.mod` with the tag we just created: - - ```bash - # Must be run from the cert-manager repo folder. - cd cmd/cmctl - go get github.com/cert-manager/cert-manager@$RELEASE_VERSION - cd ../.. - - find . -name go.mod -not -path ./_bin/\* -exec dirname '{}' \; | xargs -L1 -I@ sh -c 'cd @; go mod tidy' - git add **/go.mod **/go.sum - git commit -m"Update cmd/cmctl's go.mod to $RELEASE_VERSION" - ``` - - Third, create a tag for the `cmd/cmctl` module: - - ```bash - # Must be run from the cert-manager repo folder. - git tag -m"cmd/ctl/$RELEASE_VERSION" "cmd/ctl/$RELEASE_VERSION" - git push origin "cmd/ctl/$RELEASE_VERSION" - ``` - - > **Note:** the reason we need to do this is explained on Stack Overflow: - [how-are-versions-of-a-sub-module-managed][] - - [how-are-versions-of-a-sub-module-managed]: https://stackoverflow.com/questions/60601011/how-are-versions-of-a-sub-module-managed/60601402#60601402 - - Then, open a PR to merge that change and go back to the release branch - with the following commands: - - ```bash - gh pr create \ - --title "[Release $RELEASE_VERSION] Update cmd/cmctl's go.mod to $RELEASE_VERSION" \ - --body-file - --base $BRANCH < **Note:** This step is about creating the description that will be - > copy-pasted into the GitHub release page. The creation of the "Release - > Note" page on the website is done in a previous step. - - 1. Check that all the 4 environment variables are ready: - - ```bash - echo $RELEASE_VERSION - echo $START_TAG - echo $END_REV - echo $BRANCH - ``` - - 2. Generate `github-release-description.md` with the following command: - - ```bash - # Must be run from the cert-manager folder. - export GITHUB_TOKEN=*your-token* - git fetch origin $BRANCH - export START_SHA="$(git rev-list --reverse --ancestry-path $(git merge-base $START_TAG $BRANCH)..$BRANCH | head -1)" - release-notes --debug --repo-path cert-manager \ - --org cert-manager --repo cert-manager \ - --required-author "jetstack-bot" \ - --output github-release-description.md - ``` - -

              - The GitHub token **does not need any scope**. The token is required - only to avoid rate-limits imposed on anonymous API users. -

              - 3. Add a one-sentence summary at the top. - - 4. **(final release only)** Write the "Community" section, following the example of past releases such as [v1.12.0](https://github.com/cert-manager/cert-manager/releases/tag/v1.12.0). If there are any users who didn't make code contributions but helped in other ways (testing, PR discussion, etc), be sure to thank them here! - -11. Check that the build is complete and send Slack messages about the release: - - 1. For recent versions of cert-manager, the build will have been automatically - triggered by the tag being pushed earlier. You can check if it's complete on - the [GCB Build History](https://console.cloud.google.com/cloud-build/builds?project=cert-manager-release). - - 2. If you're releasing an older version of cert-manager (earlier than 1.10) then the automatic build - will failed because the GCB config for that build wasn't backported. - In this case, you'll need to trigger the build manually using `cmrel`, which takes about 5 minutes: - - ```bash - # Must be run from the "cert-manager/release" repo folder. - cmrel makestage --ref=$RELEASE_VERSION - ``` - - This build takes ~5 minutes. It will build all container images and create - all the manifest files, sign Helm charts and upload everything to a storage - bucket on Google Cloud. These artifacts will then be published and released - in the next steps. - - 3. In any case, send a first Slack message to `#cert-manager-dev`: - -

              - Releasing 1.2.0-alpha.2 🧵 -

              - -

              - 🔰 Please have a quick look at the build log as it might contain some unredacted - data that we forgot to hide. We try to make sure the sensitive data is - properly redacted but sometimes we forget to update this. -

              - - 4. Send a second Slack message in reply to this first message with the - Cloud Build job link. For example, the message might look like: - -

              - Follow the cmrel makestage build: https://console.cloud.google.com/cloud-build/builds/7641734d-fc3c-42e7-9e4c-85bfc4d1d547?project=1021342095237 -

              - -12. Run `cmrel publish`: - - 1. Do a `cmrel publish` dry-run to ensure that all the staged resources are - valid. Run the following command: - - ```bash - # Must be run from the "cert-manager/release" repo folder. - cmrel publish --release-name "$RELEASE_VERSION" - ``` - - You can view the progress by clicking the Google Cloud Build URL in the - output of this command. - - 2. While the build is running, send a third Slack message in reply to the first message: - -

              - Follow the `cmrel publish` dry-run build: https://console.cloud.google.com/cloud-build/builds16f6f875-0a23-4fff-b24d-3de0af207463?project=1021342095237 -

              - - 3. Now publish the release artifacts for real. The following command will publish the artifacts to GitHub, `Quay.io` and to our - [helm chart repository](https://charts.jetstack.io): - - ```bash - # Must be run from the "cert-manager/release" repo folder. - cmrel publish --nomock --release-name "$RELEASE_VERSION" - ``` - -
              - ⏰ Upon completion there will be: -
                -
              1. - A draft release of cert-manager on GitHub -
              2. -
              3. - A pull request containing the new Helm chart -
              4. -
              -
              - - 4. While the build is running, send a fourth Slack message in reply to the first message: - -

              - Follow the cmrel publish build: https://console.cloud.google.com/cloud-build/builds/b6fef12b-2e81-4486-9f1f-d00592351789?project=1021342095237 -

              - -13. Publish the GitHub release: - - 1. Visit the draft GitHub release and paste in the release notes that you - generated earlier. You will need to manually edit the content to match - the style of earlier releases. In particular, remember to remove - package-related changes. - - 2. **(initial alpha, subsequent alpha and beta only)** Tick the box "This is - a pre-release". - - 3. **(final release and patch release)** Tick the box "Set as the latest - release". - - 4. Click "Publish" to make the GitHub release live. - -14. Merge the pull request containing the Helm chart: - - The Helm charts for cert-manager are served using Cloudflare pages - and the Helm chart files and metadata are stored in the [Jetstack charts repository](https://github.com/jetstack/jetstack-charts). - The `cmrel publish --nomock` step (above) will have created a PR in this repository which you now have to review and merge, as follows: - - 1. [Visit the pull request](https://github.com/jetstack/jetstack-charts/pulls) - 2. Review the changes - 3. Fix any failing checks - 4. Merge the PR - 5. Check that the [cert-manager Helm chart is visible on ArtifactHUB](https://artifacthub.io/packages/helm/cert-manager/cert-manager). - -15. **(final + patch releases)** Merge the 4 Website PRs: - - 1. Merge the PRs "Release Notes", "Upgrade Notes", and "Freeze And Bump - Versions" that you have created previously. - 2. Create the PR "Merge release-next into master" by [clicking - here][ff-release-next]. - - If you see the label `dco-signoff: no`, add a comment on the PR with: - - ```text - /override dco - ``` - - This command is necessary because some the merge commits have been - written by the bot and do not have a DCO signoff. - - [ff-release-next]: https://github.com/cert-manager/website/compare/master...release-next?quick_pull=1&title=%5BPost-Release%5D+Merge+release-next+into+master&body=%3C%21--%0A%0AThe+command+%22%2Foverride+dco%22+is+necessary+because+some+the+merge+commits%0Ahave+been+written+by+the+bot+and+do+not+have+a+DCO+signoff.%0A%0A--%3E%0A%0A%2Foverride+dco - -16. Open a PR for a [Homebrew](https://github.com/Homebrew/homebrew-core/pulls) formula update for `cmctl`. - - Assuming you have `brew` installed, you can use the `brew bump-formula-pr` - command to do this. You'll need the new tag name and the commit hash of that - tag. See `brew bump-formula-pr --help` for up to date details, but the command - will be of the form: - - ```bash - brew bump-formula-pr --dry-run --tag v0.10.0 --revision da3265115bfd8be5780801cc6105fa857ef71965 cmctl - ``` - - Replacing the tag and revision with the new ones. - - This will take time for the Homebrew team to review. Once the pull reqeust - against https://github.com/homebrew/homebrew-core has been opened, continue - with further release steps. - -17. Post a Slack message as an answer to the first message. Toggle the check - box "Also send to `#cert-manager-dev`" so that the message is well - visible. Also cross-post the message on `#cert-manager`. - -

              - https://github.com/cert-manager/cert-manager/releases/tag/v1.0.0 🎉 -

              - -18. **(final release only)** Show the release to the world: - - 1. Send an email to - [`cert-manager-dev@googlegroups.com`](https://groups.google.com/g/cert-manager-dev) - with the `release` label - ([examples](https://groups.google.com/g/cert-manager-dev?label=release)). - - 2. Send a tweet on the cert-manager Twitter account! Login details are in Jetstack's 1password (for now). - ([Example tweet](https://twitter.com/CertManager/status/1612886311957831680)). Make sure [@JetstackHQ](https://twitter.com/JetstackHQ) retweets it! - - 3. Send a toot from the cert-manager Mastodon account! Login details are in Jetstack's 1password (for now). - ([Example toot](https://infosec.exchange/@CertManager/109666434738850493)) - -19. Proceed to the post-release "testing and release" steps: - - 1. **(initial beta only)** Create a PR on - [cert-manager/release](https://github.com/cert-manager/release) in order to - add the new release to our list of periodic ProwJobs. Use [this PR](https://github.com/cert-manager/testing/pull/774/) as an example. - - 2. **(initial beta only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and - open a PR to [cert-manager/testing](https://github.com/cert-manager/testing) adding the generated prow configs. - Use [this PR](https://github.com/cert-manager/testing/pull/766) as an example. - - 3. **(final release only)** Create a PR on - [cert-manager/release](https://github.com/cert-manager/release), - removing the now unsupported release version (2 versions back) in this file: - - ```plain - prowspecs/specs.go - ``` - - This will remove the periodic ProwJobs for this version as they're no longer needed. - - 4. **(final release only)** Run `cmrel generate-prow --branch='*' -o file` with the new version from the previous step and - open a PR to [jetstack/testing](https://github.com/cert-manager/testing) adding the generated prow configs. - - 5. **(final release only)** Open a PR to [`jetstack/testing`](https://github.com/cert-manager/testing) - and update the [milestone_applier](https://github.com/cert-manager/testing/blob/3110b68e082c3625bf0d26265be2d29e41da14b2/config/plugins.yaml#L69) - config so that newly raised PRs on master are applied to a new milestone - for the next release. E.g. if master currently points at the `v1.10` milestone, change it to point at `v1.11`. - - If the [milestone](https://github.com/cert-manager/cert-manager/milestones) for the next release doesn't exist, - create it first. If you consider the milestone for the version you just released to be complete, close it. - - 6. Open a PR against the Krew index such as [this one](https://github.com/kubernetes-sigs/krew-index/pull/1724), - bumping the versions of our kubectl plugins. This is likely only worthwhile if - cmctl / kubectl plugin functionality has changed significantly or after the first release of a new major version. - - 7. Create a new OLM package and publish to OperatorHub - - cert-manager can be [installed](https://cert-manager.io/docs/installation/operator-lifecycle-manager/) using Operator Lifecycle Manager (OLM) - so we need to create OLM packages for each cert-manager version and publish them to both - [`operatorhub.io`](https://operatorhub.io/operator/cert-manager) and the equivalent package index for RedHat OpenShift. - - Follow [the cert-manager OLM release process](https://github.com/cert-manager/cert-manager-olm#release-process) and, once published, - [verify that the cert-manager OLM installation instructions](https://cert-manager.io/docs/installation/operator-lifecycle-manager/) still work. - -## Older Releases - -The above guide only applies for versions of cert-manager from v1.8 and newer. - -Older versions were built using Bazel and this difference in build process is reflected in the release process. - -### cert-manager 1.6 and 1.7 - -Follow [this older version][older-release-process] of the release process on GitHub, rather than the guide on this website. - -The most notable difference is you'll call `cmrel stage` rather than `cmrel makestage`. You should be fine to use the latest -version of `cmrel` to do the release. - -### cert-manager 1.5 and earlier - -If you're releasing version 1.5 or earlier you must also be sure to install a different version of `cmrel`. - -In the step where you install `cmrel`, you'll want to run the following instead: - -```bash -go install github.com/cert-manager/release/cmd/cmrel@cert-manager-pre-1.6 -``` - -This will ensure that the version of `cmrel` you're using is compatible with the version of cert-manager you're releasing. - -In addition, when you check out the `cert-manager/release` repository you should be sure to check out the `cert-manager-pre-1.6` tag in that repo: - -```bash -git checkout cert-manager-pre-1.6 -``` - -Other than the different `cert-manager/release` tag and `cmrel` version, you can follow the [same older release documentation][older-release-process] as -is used for 1.6 and 1.7 - just remember to change the version of `cmrel` you install! - -[older-release-process]: https://github.com/cert-manager/website/blob/6fa0db74de0ae17d7be638a08155d1b4e036aaa9/content/en/docs/contributing/release-process.md?plain=1 diff --git a/content/v1.13-docs/contributing/security.md b/content/v1.13-docs/contributing/security.md deleted file mode 100644 index 6d33165ede..0000000000 --- a/content/v1.13-docs/contributing/security.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Reporting Security Issues -description: 'cert-manager contributing: Security policy' ---- - -Security is the number one priority for cert-manager. If you think you've -found a vulnerability in any cert-manager project, please follow the -[vulnerability reporting process](https://github.com/cert-manager/cert-manager/blob/master/SECURITY.md) -documented in the main cert-manager repository. - -The reporting process is the same for all repositories under the -cert-manager organization. The process is documented in one place to ensure -a single source of truth and a single list of [security contacts](https://github.com/cert-manager/cert-manager/blob/master/SECURITY_CONTACTS.md). \ No newline at end of file diff --git a/content/v1.13-docs/contributing/sign-off.md b/content/v1.13-docs/contributing/sign-off.md deleted file mode 100644 index 2e0dd8cbfa..0000000000 --- a/content/v1.13-docs/contributing/sign-off.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: DCO Sign Off -description: 'cert-manager contributing: DCO Sign-off' ---- - -All contributors to the project retain copyright to their work, but must only submit -work which they have the rights to submit. - -We require all contributors to acknowledge that they have the rights to the code they're contributing -by signing their commits in git using a "DCO Sign Off". Note that this is different to "commit signing" -using something like PGP or [`gitsign`](https://github.com/sigstore/gitsign)! - -Any copyright notices in a cert-manager repo should specify the authors as -"The cert-manager Authors". - -To sign your work, pass the `--signoff` option to `git commit` or `git rebase`: - -```bash -# Sign off a commit as you're making it -git commit --signoff -m"my commit" - -# Add a signoff to the last commit you made -git commit --amend --signoff - -# Rebase your branch against master and sign off every commit in your branch -git rebase --signoff master -``` - -This will add a line similar to the following at the end of your commit: - -```text -Signed-off-by: Joe Bloggs -``` - -By signing off a commit you're stating that you certify the following: - -```text -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -1 Letterman Drive -Suite D4700 -San Francisco, CA, 94129 - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` - -That statement is taken from [https://developercertificate.org/](https://developercertificate.org/). diff --git a/content/v1.13-docs/contributing/signing-keys.md b/content/v1.13-docs/contributing/signing-keys.md deleted file mode 100644 index 74d5382d86..0000000000 --- a/content/v1.13-docs/contributing/signing-keys.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Signing Keys -description: 'cert-manager contributing: Code signing / Signing keys' ---- - -This page describes the bootstrapping process for a key, including how to do it and why a bootstrapping -process is required. - -## What do we Serve? - -To facilitate verification of signatures, we serve public key information from the cert-manager website -directly. It's important to serve the keys from a different location to where the artifacts are hosted; if the -keys were hosted at the same location as the artifacts, an attacker able to change the artifacts would be able -to also change the keys! - -We serve several key types under `static/public-keys`: - -- `cert-manager-pgp-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.asc`: ASCII-armored PGP public key, used for verifying signatures on helm charts via `helm verify` (after being converted to a keyring) -- `cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg`: Old style GPG keyring, needed by the `--keyring` parameter to `helm verify`. See Keyring below. -- `cert-manager-pubkey-2021-09-20.pem`: The raw, PEM-encoded public key used for signing. Cannot be used with GPG (and therefore helm), but should be used for other verification types. - -## Background / Architecture - -Code signing for cert-manager artifacts is done entirely using cloud KMS keys, to ensure that nobody -can get access to the private keys in plain-text; all signing operations using the key are therefore -done through cloud APIs and are logged. - -Currently, all keys are on Google KMS, since the rest of cert-manager's release infrastructure is also -in GCP. The key - and the role bindings which allow access to it - are specified in terraform in a closed -source Jetstack repo. - -## Why Bootstrap? - -While the private key is not retrievable for a KMS key, the public key is and _must_ be retrieved so that -end-users can verify signatures made by the key. In GCP, retrieving the public key is itself an -[API call](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys.cryptoKeyVersions/getPublicKey) -which returns the raw key in a PEM encoded format. - -That PEM-encoded public key works for some cases (e.g. verifying container signature made using `cosign`) but -it's not sufficient for Helm chart verification, since Helm chart signing (sadly) requires the use of PGP. - -## Bootstrapping a PGP Identity - -It's possible to use a shim to use GCP KMS as a PGP key which enables us to avoid having two separate signing keys, -but PGP public identities are slightly more complicated than plain public keys; they also contain a name, -creation time, comment and email address to identify the signer. This public "identity" must itself be signed by the -private key (to prove that the information in the identity is legitimate). - -This bootstrapping can be done using the cert-manager release tool, `cmrel`: - -```console -# note that the key name might not exactly match this in the future -$ cmrel bootstrap-pgp --key "projects/cert-manager-release/locations/europe-west1/keyRings/cert-manager-release/cryptoKeys/cert-manager-release-signing-key/cryptoKeyVersions/1" -``` - -This will trigger a cloud build job which will output both the armored PGP identity and the raw PEM public key; the values -can be copied from the job output. - -### GPG Keyring - -As an additional UX feature, we can also generate a GPG keyring from the PGP identity, since the keyring is what's required -by the Helm CLI to actually validate a chart: - -```console -# Example of verifying a chart. -$ helm verify --keyring cert_manager_keyring_1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg /path/to/chart.tgz -Signed by: cert-manager Maintainers -Using Key With Fingerprint: 1020.... -Chart Hash Verified: sha256:bb86... -``` - -The keyring can be generated using [this script](https://github.com/cert-manager/release/blob/a219e18b2e64ef078bf73b3641d589b43d1fccb8/hack/helm_keyring.sh). \ No newline at end of file diff --git a/content/v1.13-docs/contributing/third-party-code-donation.md b/content/v1.13-docs/contributing/third-party-code-donation.md deleted file mode 100644 index f53da8e3c6..0000000000 --- a/content/v1.13-docs/contributing/third-party-code-donation.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: Donating Third Party Code to cert-manager -description: 'cert-manager contributing: Third party code donations' ---- - -The cert-manager project welcomes external contributions and has benefited greatly from thousands -of commits from hundreds of different contributors. Most code is usually committed through pull -requests to a specific repo, whether that be the main cert-manager repository or one of the associated -repositories such as the website. - -Some contributions aren't as well suited to that kind of workflow, however. That would most likely -be because their functionality doesn't belong in any particular existing cert-manager repo, while still -relating to the cert-manager project. - -This document aims to address the donation of code to the cert-manager project, and to provide a -framework for sustainable contributions which can be tested and relied upon going forwards by both -cert-manager maintainers and users. - -The requirements in this document are based in part on what's done for CoreDNS, Envoy, Kubernetes -and containerd. - -## Requirements - -1. Code must be licensed appropriately, including any dependencies - We'd prefer [Apache 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) since that's - what cert-manager [uses](https://github.com/cert-manager/cert-manager/blob/master/LICENSE), but the - license must be [OSI approved](https://opensource.org/licenses). -2. Code must conform to CNCF standards and due diligence requirements - You don't need to go over this with a fine-toothed comb; the intent here is that no code donation - should have a negative effect on cert-manager's progress as a CNCF project. See the - [CNCF due diligence template](https://github.com/cncf/toc/blob/main/process/dd-review-template.md) -3. Must be sponsored by an existing maintainer - An existing regular contributor to cert-manager must sponsor the adoption of any third party code - donation. This ensures that there's a single point of contact for the party donating the code. -4. Must pass cert-manager conformance tests - This might not apply to all donations, but where conformance tests exist any donated code must - pass them. E.g. for [external issuers](https://github.com/cert-manager/cert-manager/blob/dffbf391dbb0fc6c1cfea62e561a9c6f54362ab0/test/e2e/suite/conformance/certificates/external/external.go#L41-L62) -5. Must provide a point-of-contact for questions about the project for at least 3 months after acceptance - We don't anticipate that we'd need to reach out often after the donation has been accepted, - but it's important to have someone we can reach out to if we need to. -6. The donation must be a defined extension type or justify why it doesn't belong in the main repositories - E.g. an ACME DNS solver, a custom issuer or an ACME HTTP solver -7. Code must have a similar level of quality to cert-manager itself - This could be enforced by, for example, running static analysis tools on the code base similar to - those used by cert-manager. -8. Code must have a non-trivial test suite, including both unit tests and end-to-end tests - These tests must be able to be run in their entirety after a PR is raised against the repo. We don't - need 100% code coverage, but there should be tests for important functionality. -9. The project must adopt the cert-manager security policy and link back to the policy, as in e.g. - the [istio-csr `SECURITY.md`](https://github.com/cert-manager/istio-csr/blob/master/SECURITY.md) -10. Must have DCO sign-offs or coverage for all commits - To ensure that all code can legally be donated, all commits should have DCO sign-off or else have - a positive affirmation made by each contributor prior to donation. See below. - -## Preferences - -These items are not absolutely necessary but they definitely help if a code donation is to be accepted. - -- Should be written in Go - We don't _need_ code to be written in Go, but we'd much prefer that it is. Since cert-manager itself - is written in Go, code donations in Go allow us to use existing experience and tooling on Go code. - -## DCO Signoff - -As a method of ensuring that the donator has permission to donate the code, we require DCO sign-offs - -or something equivalent - to be in place at the time of the donation. - -The cert-manager [DCO signoff process](https://cert-manager.io/docs/contributing/sign-off/) -would be appropriate. Existing contributors could bootstrap this process by creating an empty signed-off -with a note that previous code should be considered signed off as of that commit: - -```bash -git commit --allow-empty --signoff --message="bootstrapping DCO signoff for past commits" -``` - -## After Donation - -Code files in the donated repository must be updated to include the relevant -[cert-manager boilerplate](https://github.com/cert-manager/cert-manager/blob/master/hack/boilerplate/boilerplate.go.txt) \ No newline at end of file diff --git a/content/v1.13-docs/installation/supported-releases.md b/content/v1.13-docs/installation/supported-releases.md deleted file mode 100644 index 29e5ba4d9a..0000000000 --- a/content/v1.13-docs/installation/supported-releases.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -title: Supported Releases -description: Supported releases, Kubernetes versions, OpenShift versions and upcoming release timeline ---- - -{/* -Inspired by https://istio.io/latest/about/supported-releases/ -*/} - -This page lists the status, timeline and policy for currently supported releases. - -Each release is supported for a period of four months, and we aim to create a new -release roughly every two months, accounting for holiday periods, major conferences -and other world events. - - -## Currently supported releases - -| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| -| [1.13][] | Sep 12, 2023 | Release of 1.15 | 1.23 → 1.28 | 4.10 → 4.15 | -| [1.13.0] | May 19, 2023 | Release of 1.14 | 1.22 → 1.27 | 4.9 → 4.14 | - -## Upcoming releases - -| Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | -|----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| -| [1.14][] | Jan 15, 2024 | ~4 months post release | TBD | TBD | - -Dates in the future are uncertain and might change. - -## Old releases - -| Release | Release Date | EOL | Compatible Kubernetes versions | Compatible OpenShift versions | -|----------|:------------:|:------------:|:------------------------------:|:-----------------------------:| -| [1.11][] | Jan 11, 2023 | Sep 12, 2023 | 1.21 → 1.27 | 4.8 → 4.14 | -| [1.10][] | Oct 17, 2022 | May 19, 2023 | 1.20 → 1.26 | 4.7 → 4.13 | -| [1.9][] | Jul 22, 2022 | Jan 11, 2023 | 1.20 → 1.24 | 4.7 → 4.11 | -| [1.8][] | Apr 05, 2022 | Oct 17, 2022 | 1.19 → 1.24 | 4.6 → 4.11 | -| [1.7][] | Jan 26, 2021 | Jul 22, 2022 | 1.18 → 1.23 | 4.5 → 4.9 | -| [1.6][] | Oct 26, 2021 | Apr 05, 2022 | 1.17 → 1.22 | 4.4 → 4.9 | -| [1.5][] | Aug 11, 2021 | Jan 26, 2022 | 1.16 → 1.22 | 4.3 → 4.8 | -| [1.4][] | Jun 15, 2021 | Oct 26, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | -| [1.3][] | Apr 08, 2021 | Aug 11, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | -| [1.2][] | Feb 10, 2021 | Jun 15, 2021 | 1.16 → 1.21 | 4.3 → 4.7 | -| [1.1][] | Nov 24, 2020 | Apr 08, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | -| [1.0][] | Sep 02, 2020 | Feb 10, 2021 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.16][] | Jul 23, 2020 | Nov 24, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.15][] | May 06, 2020 | Sep 02, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.14][] | Mar 11, 2020 | Jul 23, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.13][] | Jan 21, 2020 | May 06, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.12][] | Nov 27, 2019 | Mar 11, 2020 | 1.11 → 1.21 | 3.11 → 4.7 | -| [0.11][] | Oct 10, 2019 | Jan 21, 2020 | 1.9 → 1.21 | 3.09 → 4.7 | - -[s]: #kubernetes-supported-versions -[1.13]: https://github.com/cert-manager/cert-manager/milestone/34 -[1.13.0 https://cert-manager.io/docs/release-notes/release-notes-1.12 -[1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 -[1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 -[1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 -[1.8]: https://cert-manager.io/docs/release-notes/release-notes-1.8 -[1.7]: https://cert-manager.io/docs/release-notes/release-notes-1.7 -[1.6]: https://cert-manager.io/docs/release-notes/release-notes-1.6 -[1.5]: https://cert-manager.io/docs/release-notes/release-notes-1.5 -[1.4]: https://cert-manager.io/docs/release-notes/release-notes-1.4 -[1.3]: https://cert-manager.io/docs/release-notes/release-notes-1.3 -[1.2]: https://cert-manager.io/docs/release-notes/release-notes-1.2 -[1.1]: https://cert-manager.io/docs/release-notes/release-notes-1.1 -[1.0]: https://cert-manager.io/docs/release-notes/release-notes-1.0 -[0.16]: https://cert-manager.io/docs/release-notes/release-notes-0.16 -[0.15]: https://cert-manager.io/docs/release-notes/release-notes-0.15 -[0.14]: https://cert-manager.io/docs/release-notes/release-notes-0.14 -[0.13]: https://cert-manager.io/docs/release-notes/release-notes-0.13 -[0.12]: https://cert-manager.io/docs/release-notes/release-notes-0.12 -[0.11]: https://cert-manager.io/docs/release-notes/release-notes-0.11 - -We list cert-manager releases on [GitHub](https://github.com/cert-manager/cert-manager/releases), -and release notes on [cert-manager.io](https://cert-manager.io/docs/release-notes/). - -We also maintain detailed [upgrade instructions](https://cert-manager.io/docs/installation/upgrading/). - -## Support policy - -### What we mean by support - -Our support window is four months for each release branch. In the below -diagram, `release-1.2` is an example of a release branch. The support -window corresponds to the two latest releases, given that we produce a new -final release every two months. We offer two types of support: - -- [Technical support](#technical-support), -- [Security and bug fixes](#bug-fixes-support). - -For example, imagining that the latest release is `v1.2.0`, you can expect -support for both `v1.2.0` and `v1.1.0`. Only the last patch release of each -branch is actually supported. - -```diagram - v1.0.0 ^ - Sep 2, 2020 | UNSUPPORTED -------+---------------------------------------------> release-1.0 | RELEASES - \ v - \ - \ v1.1.0 - \ Nov 24, 2020 ^ - ---------+-------------------------------> release-1.1 | - \ | SUPPORTED - \ | RELEASES - \ v1.2.0 | = the two - \ Feb 10, 2021 | last - ------------+--------------> release-1.2 | releases - \ v - \ - \ - \ - -----------> master branch - April 1, 2021 -``` - - -### Technical support - -Technical assistance is offered on a best-effort basis for supported -releases only. You can request support from the community on [Kubernetes -Slack](https://slack.k8s.io/) (in the `#cert-manager` channel), using -[GitHub Discussions][discussions] or using the [cert-manager-dev][group] -Google group. - -[discussions]: https://github.com/cert-manager/cert-manager/discussions -[group]: https://groups.google.com/g/cert-manager-dev - - -### Security and bug fixes - -We back-port important bug fixes — including security fixes — to all -currently supported releases. - -- [Security issues](#security-issues), -- [Critical bugs](#critical-bugs), -- [Long-standing bugs](#long-standing-bugs). - - -#### Security issues - -**Security issues** are fixed as soon as possible. They get back-ported to -the last two releases, and a new patch release is immediately created for them. - - -#### Critical bugs - -**Critical bugs** include both regression bugs as well as upgrade bugs. - -Regressions are functionalities that worked in a previous release but no longer -work. [#4142][], [#3393][] and [#2857][] are three examples of regressions. - -Upgrade bugs are issues (often Helm-related) preventing users from -upgrading to currently supported releases from earlier releases of -cert-manager. [#3882][] and [#3644][] are examples of upgrade bugs. - -Note that [intentional breaking changes](#breaking-changes) do not belong to -this category. - -Fixes for critical bugs are (usually) immediately back-ported by creating a new -patch release for the currently supported releases. - - -#### Long-standing bugs - -**Long-standing bug**: sometimes a bug exists for a long time, and may have -known workarounds. [#3444][] is an example of a long-standing bug. - -Where we feel that back-porting would be difficult or might be a stability -risk to clusters running cert-manager, we'll make the fix in a major -release but avoid back-porting the fix. - - -#### Breaking changes - -Breaking changes are changes that intentionally break the cert-manager -Kubernetes API or the command line flags. We avoid making breaking changes -where possible, and where they're required we'll give as much notice as -possible. - - -#### Other back-ports - -We aim to be conservative in what we back-port. That applies especially for anything which -could be a _runtime_ change - that is, a change which might alter behavior for someone -upgrading between patch releases. - -That means that if a candidate for back-porting has a chance of having a runtime impact we're -unlikely to accept the change unless it addresses a security issue or a critical bug. - -We reserve the right to back-port other changes which are unlikely to have a runtime impact, such as -documentation or tooling changes. An example would be [#5209][] which updated how we perform a release of -cert-manager but didn't have any realistic chance of having a runtime impact. - -Generally we'll seek to be pragmatic. A rule of thumb might be to ask: - -"Does this back-port improve cert-manager, bearing in mind that we really value stability for already-released versions?" - -[#3393]: https://github.com/cert-manager/cert-manager/issues/3393 "Broken CloudFlare DNS01 challenge" -[#2857]: https://github.com/cert-manager/cert-manager/issues/2857 "CloudDNS DNS01 challenge crashes cert-manager" -[#4142]: https://github.com/cert-manager/cert-manager/issues/4142 "Cannot issue a certificate that has the same subject and issuer" -[#3444]: https://github.com/cert-manager/cert-manager/issues/3444 "Certificates do not get immediately updated after updating them" -[#3882]: https://github.com/cert-manager/cert-manager/pull/3882 "Certificate's revision history limit validated by webhook" -[#3644]: https://github.com/cert-manager/cert-manager/issues/3644 "Helm upgrade from v1.2 to v1.2 impossible due to a Helm bug" -[#5209]: https://github.com/cert-manager/cert-manager/pull/5209 "release-1.8: rclone" - - - -## How we determine supported Kubernetes versions - -The list of supported Kubernetes versions displayed in the [Supported Releases](#supported-releases) section -depends on what the cert-manager maintainers think is reasonable to support and to test. - -In practice, this is largely determined based on what versions of [kind](https://github.com/kubernetes-sigs/kind) -are available for testing, and which versions of Kubernetes are provided by major upstream cloud Kubernetes vendors -including EKS, GKE, AKS and OpenShift. - -| Vendor | Oldest Kubernetes Release\* | Other Older Kubernetes Releases | -|:-----------------:|-----------------------------|------------------------------------------------------------------------------------| -| [EKS][eks] | 1.23 (EOL Oct 2023) | 1.24 (EOL Jan 2024), 1.25 (EOL May 2024), 1.26 (EOL Jun 2024), 1.28 (EOL Nov 2024) | -| [GKE][gke] | 1.24 (EOL Oct 2023) | 1.25 (EOL Feb 2024), 1.26 (EOL May 2024), 1.27 (EOL Jan 2025), 1.28 (EOL -) | -| [AKS][aks] | 1.25 (EOL Dec 2023) | 1.26 (EOL Mar 2024), 1.27 (EOL Jun 2024), 1.28 (EOL -) | -| [OpenShift 4][os] | 1.23 (4.10, EOL Sep 2023) | 1.24 (4.11, EOL Feb 2024), 1.25 (4.12, EOL Jan 2025), 1.25 (4.13, EOL Nov 2024) | - -\*Oldest release relevant to the next cert-manager release, as of 2023-09-13 - -[eks]: https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html#kubernetes-release-calendar -[gke]: https://cloud.google.com/kubernetes-engine/docs/release-schedule -[aks]: https://docs.microsoft.com/en-us/azure/aks/supported-kubernetes-versions#aks-kubernetes-release-calendar -[os]: https://access.redhat.com/support/policy/updates/openshift#dates - -### OpenShift - -cert-manager supports versions of OpenShift 4 based on the version of Kubernetes -that each version maps to. - -For convenience, the following table shows these version mappings: - -| OpenShift versions | Kubernetes version | -|--------------------|--------------------| -| 4.15 | 1.28 | -| 4.14 | 1.27 | -| 4.13 | 1.26 | -| 4.12 | 1.25 | -| 4.11 | 1.24 | -| 4.10, 4.10 EUS | 1.23 | -| 4.9 | 1.22 | -| 4.8, 4.8 EUS | 1.21 | -| 4.7 | 1.20 | -| 4.6, 4.6 EUS | 1.19 | - -Note that some OpenShift versions listed above may be predicted, since an updated version of OpenShift may -not yet be available for the latest Kubernetes releases. - -The last version of cert-manager to support OpenShift 3 was cert-manager 1.2, which is -no longer maintained. - -## Terminology - -The term "release" (or "minor release") refers to one minor version of -cert-manager. For example, 1.2 and 1.3 are two releases. Note that we do -not use the prefix `v` for releases (just "1.2"). This is because releases -are not used as git tags. - -Patch releases use the `v` prefix (e.g., `v1.2.0`, `v1.3.1`...) since one -patch release = one git tag. The initial patch release is called "final -release": - -| Type of release | Example of git tag | Corresponding release | Corresponding release branch\* | -| --------------- | ------------------ | --------------------- | ------------------------------ | -| Final release | `v1.3.0` | 1.3 | `release-1.3` | -| Patch release | `v1.3.1` | 1.3 | `release-1.3` | -| Pre-release | `v1.4.0-alpha.0` | N/A\*\* | `release-1.4` | - -\*For maintainers: each release has an associated long-lived branch that we -call the “release branch”. For example, `release-1.2` is the release branch -for release 1.2. - -\*\*Pre-releases (e.g., `v1.3.0-alpha.0`) don't have a corresponding -release (e.g., 1.3) since a release only exists after a final release -(e.g., `v1.3.0`) has been created. - -Our naming scheme mostly follows [Semantic Versioning -2.0.0](https://semver.org/) with `v` prepended to git tags and docker -images: - -```plain -v.. -``` - -where `` is increased for each release, and `` counts the -number of patches for the current `` release. A patch is usually a -small change relative to the `` release. diff --git a/content/v1.13-docs/manifest.json b/content/v1.13-docs/manifest.json index 904792e461..c2d48af436 100644 --- a/content/v1.13-docs/manifest.json +++ b/content/v1.13-docs/manifest.json @@ -201,53 +201,6 @@ } ] }, - { - "title": "Projects", - "routes": [ - { - "title": "Contents", - "path": "/v1.13-docs/projects/README.md" - }, - { - "title": "istio-csr", - "path": "/v1.13-docs/projects/istio-csr.md" - }, - { - "title": "csi-driver", - "path": "/v1.13-docs/projects/csi-driver.md" - }, - { - "title": "csi-driver-spiffe", - "path": "/v1.13-docs/projects/csi-driver-spiffe.md" - }, - { - "title": "approver-policy", - "routes": [ - { - "title": "Introduction", - "path": "/v1.13-docs/projects/approver-policy/README.md" - }, - { - "title": "API Reference", - "path": "/v1.13-docs/projects/approver-policy/api-reference.md" - } - ] - }, - { - "title": "trust-manager", - "routes": [ - { - "title": "Introduction", - "path": "/v1.13-docs/projects/trust-manager/README.md" - }, - { - "title": "API Reference", - "path": "/v1.13-docs/projects/trust-manager/api-reference.md" - } - ] - } - ] - }, { "title": "Tutorials", "routes": [ @@ -330,101 +283,6 @@ "title": "FAQ", "path": "/v1.13-docs/faq/README.md" }, - { - "title": "Contributing", - "routes": [ - { - "title": "Introduction", - "path": "/v1.13-docs/contributing/README.md" - }, - { - "title": "Feature Policy", - "path": "/v1.13-docs/contributing/policy.md" - }, - { - "title": "Building cert-manager", - "path": "/v1.13-docs/contributing/building.md" - }, - { - "title": "Contributing Flow", - "path": "/v1.13-docs/contributing/contributing-flow.md" - }, - { - "title": "CRDs", - "path": "/v1.13-docs/contributing/crds.md" - }, - { - "title": "DNS Providers", - "path": "/v1.13-docs/contributing/dns-providers.md" - }, - { - "title": "Running End-to-End Tests", - "path": "/v1.13-docs/contributing/e2e.md" - }, - { - "title": "Implementing External Issuers", - "path": "/v1.13-docs/contributing/external-issuers.md" - }, - { - "title": "DCO Sign Off", - "path": "/v1.13-docs/contributing/sign-off.md" - }, - { - "title": "Release Process", - "path": "/v1.13-docs/contributing/release-process.md" - }, - { - "title": "Developing with Kind", - "path": "/v1.13-docs/contributing/kind.md" - }, - { - "title": "Implementing Feature Gates", - "path": "/v1.13-docs/contributing/featuregates.md" - }, - { - "title": "Google Season of Docs", - "routes": [ - { - "title": "Introduction", - "path": "/v1.13-docs/contributing/google-season-of-docs/README.md" - }, - { - "title": "2022", - "routes": [ - { - "title": "Introduction", - "path": "/v1.13-docs/contributing/google-season-of-docs/2022/README.md" - }, - { - "title": "Improve the Navigation and Structure of the cert-manager Website", - "path": "/v1.13-docs/contributing/google-season-of-docs/2022/improve-navigation-and-structure.md" - } - ] - } - ] - }, - { - "title": "Reporting Security Issues", - "path": "/v1.13-docs/contributing/security.md" - }, - { - "title": "Coding Conventions", - "path": "/v1.13-docs/contributing/coding-conventions.md" - }, - { - "title": "Third Party Code Donations", - "path": "/v1.13-docs/contributing/third-party-code-donation.md" - }, - { - "title": "Signing Keys", - "path": "/v1.13-docs/contributing/signing-keys.md" - }, - { - "title": "Importing cert-manager in Go", - "path": "/v1.13-docs/contributing/importing.md" - } - ] - }, { "title": "Concepts", "routes": [ diff --git a/content/v1.13-docs/projects/README.md b/content/v1.13-docs/projects/README.md deleted file mode 100644 index 185df2fce4..0000000000 --- a/content/v1.13-docs/projects/README.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Projects -description: 'Satellite Projects of cert-manager' ---- - -The cert-manager project has a number of [satellite projects](https://github.com/cert-manager) -that extend the project's functionality, and complement the core cert-manager feature-set. - -These tools help with security, compliance and control. - -- [istio-csr](./istio-csr.md): Secure Istio service mesh with istio-csr which is - an agent that allows for [Istio](https://istio.io) workload and control plane - components to be secured using cert-manager. -- [approver-policy](./approver-policy/README.md): - a cert-manager **approver** that will automatically approve or deny - certificate requests based on defined policy. -- [csi-driver](./csi-driver.md): - a Container Storage Interface (CSI) driver plugin for Kubernetes to work along - cert-manager. The goal for this plugin is to seamlessly request and mount - certificate key pairs to pods. This is useful for facilitating mTLS, or - otherwise securing connections of pods with guaranteed present certificates - whilst having all of the features that cert-manager provides. -- [csi-driver-spiffe](./csi-driver-spiffe.md): - another CSI driver plugin to work along cert-manager. This CSI driver - transparently delivers [SPIFFE](https://spiffe.io/) - [SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-verifiable-identity-document-svid) - in the form of X.509 certificate key pairs to mounting Kubernetes Pods. The - end result is all and any Pod running in Kubernetes can securely request their - SPIFFE identity document from a Trust Domain with minimal configuration. -- [trust-manager](./trust-manager/README.md): the easiest way to manage TLS trust bundles in Kubernetes and OpenShift clusters. -- [trust-manager API reference](./trust-manager/api-reference.md): full documentation of the trust-manager CRD(s) diff --git a/content/v1.13-docs/projects/approver-policy/README.md b/content/v1.13-docs/projects/approver-policy/README.md deleted file mode 100644 index bdad17d829..0000000000 --- a/content/v1.13-docs/projects/approver-policy/README.md +++ /dev/null @@ -1,431 +0,0 @@ ---- -title: approver-policy -description: 'Policy plugin for cert-manager' ---- - -approver-policy is a cert-manager -[approver](../../concepts/certificaterequest.md#approval) -that will approve or deny CertificateRequests based on policies defined in -the `CertificateRequestPolicy` custom resource. - -## Prerequisites - -[cert-manager must be installed](../../installation/README.md), and -the [the default approver in cert-manager must be disabled](../../concepts/certificaterequest.md#approver-controller). - -> ⚠️ If the default approver is not disabled in cert-manager, approver-policy will -> race with cert-manager and policy will be ineffective. - -If you install cert-manager using `helm install` or `helm upgrade`, -you can disable the default approver by [Customizing the Chart Before Installing](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing) using the `--set` or `--values` command line flags: - -``` -# Example --set value ---set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver -``` - -```yaml -# Example --values file content -extraArgs: - - "--controllers=*,-certificaterequests-approver" # ⚠ Disable cert-manager's built-in approver -``` - -Here's a full example which will install cert-manager or reconfigure it if it is already installed: - -```terminal -helm upgrade cert-manager jetstack/cert-manager \ - --install \ - --create-namespace \ - --namespace cert-manager \ - --version REPLACE-WITH-YOUR-CERT-MANAGER-VERSION \ - --set installCRDs=true \ - --set extraArgs={--controllers='*\,-certificaterequests-approver'} # ⚠ Disable cert-manager's built-in approver -``` - -> ℹ️ The `--set installCRDs=true` setting is a convenient way to install the -> cert-manager CRDS, but it is optional and has some drawbacks. -> Read [Helm: Installing Custom Resource Definitions](https://deploy-preview-1216--cert-manager-website.netlify.app/docs/installation/helm/#3-install-customresourcedefinitions) to learn more. -> -> ℹ️ Be sure to customize the cert-manager controller `extraArgs`, -> which are at the top level of the values file. -> *Do not* change the `webhook.extraArgs`, `startupAPICheck.extraArgs` or `cainjector.extraArgs` settings. -> -> ⚠️ If you are reconfiguring an already installed cert-manager, -> check whether the original installation already customized the `extraArgs` value -> by running `helm get values cert-manager --namespace cert-manager`. -> If there are already `extraArgs` values, merge those with the extra `--controllers` value. -> Otherwise your original `extraArgs` values will be overwritten. - -## Installation - -To install approver-policy: - -```terminal -helm repo add jetstack https://charts.jetstack.io --force-update -helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait -``` - -If you are using approver-policy with [external -issuers](../../configuration/external.md), you _must_ -include their signer names so that approver-policy has permissions to approve -and deny CertificateRequests that -[reference them](../../concepts/certificaterequest.md#rbac-syntax). -For example, if using approver-policy for the internal issuer types, along with -[google-cas-issuer](https://github.com/jetstack/google-cas-issuer), and -[aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer), -set the following values when installing: - -```terminal -$ helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manager-approver-policy --wait \ - --set app.approveSignerNames="{\ -issuers.cert-manager.io/*,clusterissuers.cert-manager.io/*,\ -googlecasclusterissuers.cas-issuer.jetstack.io/*,googlecasissuers.cas-issuer.jetstack.io/*,\ -awspcaclusterissuers.awspca.cert-manager.io/*,awspcaissuers.awspca.cert-manager.io/*\ -}" -``` - -## Configuration - -> Example policy resources can be found -> [here](https://github.com/cert-manager/approver-policy/tree/main/docs/examples). - -When a CertificateRequest is created, approver-policy will evaluate whether the -request is appropriate for any existing policy, and if so, evaluate whether it -should be approved or denied. - -For a CertificateRequest to be appropriate for a policy and therefore be -evaluated by it, it must be both bound via RBAC _and_ be selected by the policy -selector. CertificateRequestPolicy currently only supports `issuerRef` as a -selector. - -**If at least one policy permits the request, the request is approved. If at -least one policy is appropriate for the request but none of those permit the -request, the request is denied.** - -A denied CertificateRequest is considered to be permanently failed. If it was -created for a Certificate resource, the issuance will be retried with -[exponential backoff](../../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) -like all other permanent issuance failures. A CertificateRequest that is neither -approved nor denied (because no matching policy was found) will not be further -processed by cert-manager until it gets either approved or denied. - -CertificateRequestPolicies are cluster scoped resources that can be thought of -as "policy profiles". They describe any request that is approved by that -policy. Policies are bound to Kubernetes users and ServiceAccounts using RBAC. - -Below is an example of a policy that is bound to all Kubernetes users who may -only request certificates that have the common name of `"hello.world"`. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: test-policy -spec: - allowed: - commonName: - value: "hello.world" - required: true - selector: - # Select all IssuerRef - issuerRef: {} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: cert-manager-policy:hello-world -rules: - - apiGroups: ["policy.cert-manager.io"] - resources: ["certificaterequestpolicies"] - verbs: ["use"] - # Name of the CertificateRequestPolicies to be used. - resourceNames: ["test-policy"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: cert-manager-policy:hello-world -roleRef: -# ClusterRole or Role _must_ be bound to a user for the policy to be considered. - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cert-manager-policy:hello-world -subjects: -# The users who should be bound to the policies defined. -# Note that in the case of users creating Certificate resources, cert-manager -# is the entity that is creating the actual CertificateRequests, and so the -# cert-manager controller's -# Service Account should be bound instead. -- kind: Group - name: system:authenticated - apiGroup: rbac.authorization.k8s.io -``` - -## Behavior - -CertificateRequestPolicy are split into 4 parts; `allowed`, `contraints`, -`selector`, and `plugins`. - -### Allowed - -Allowed is the block that defines attributes that match against the -corresponding attribute in the request. A request is permitted by the policy if -the request omits an allowed attribute, but will _deny_ the request if it -contains an attribute which is _not_ present in the allowed block. - -An allowed attribute can be marked as `required`, which if true, will enforce -that the attribute has been defined in the request. A field can only be marked -as `required` if the corresponding field is also defined. The `required` field -is not available for `isCA` or `usages`. - -In the following CertificateRequestPolicy, a request will be permitted if it -does not request a DNS name, requests the DNS name `"example.com"`, but will be -denied when requesting `"bar.example.com"`. - -```yaml -spec: - ... - allowed: - dnsNames: - values: - - "example.com" - - "foo.example.com" - ... -``` - -In the following, a request will be denied if the request contains no Common -Name, but will permit requests whose Common Name ends in ".com". - -```yaml -spec: - ... - allowed: - commonName: - value: "*.com" - required: true - ... -``` - -If an allowed field is omitted, that attribute is considered "deny all" for -requests. - -Allowed string fields accept wildcards "\*" within its values. Wildcards "\*" in -patterns represent any string that has a length of 0 or more. A pattern -containing only "\*" will match anything. A pattern containing `"\*foo"` will -match `"foo"` as well as any string which ends in `"foo"` (e.g. `"bar-foo"`). A -pattern containing `"\*.foo"` will match `"bar-123.foo"`, but not `"barfoo"`. - -Allowed fields that are lists will permit requests that are a subset of that -list. This means that if `usages` contains `["server auth", "client auth"]`, -then a request containing only `["server auth"]` would be permitted, but not -`["server auth", "cert sign"]`. - -Below is an example including all supported allowed fields of -CertificateRequestPolicy. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: my-policy -spec: - allowed: - commonName: - value: "example.com" - dnsNames: - values: - - "example.com" - - "*.example.com" - ipAddresses: - values: - - "1.2.3.4" - - "10.0.1.*" - uris: - values: - - "spiffe://example.org/ns/*/sa/*" - emailAddresses: - values: - - "*@example.com" - required: true - isCA: false - usages: - - "server auth" - - "client auth" - subject: - organizations: - values: ["hello-world"] - countries: - values: ["*"] - organizationalUnits: - values: ["*"] - localities: - values: ["*"] - provinces: - values: ["*"] - streetAddresses: - values: ["*"] - postalCodes: - values: ["*"] - serialNumber: - value: "*" - ... -``` - -### Constraints - -Constraints is the block that is used to limit what attributes the request can -have. If a constraint is not defined, then the attribute is considered "allow -all". - -Below is an example containing all supported constraints fields of -CertificateRequestPolicy. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: my-policy -spec: - ... - constraints: - minDuration: 1h - maxDuration: 24h - privateKey: - algorithm: RSA - minSize: 2048 - maxSize: 4096 - ... -``` - -### Selector - -Selector is a required field that is used for matching -CertificateRequestPolicies against CertificateRequests for evaluation. A -CertificateRequestPolicy must select, and therefore match, a CertificateRequest -for it to be considered for evaluation of the request. - -> ⚠️ Note that the user must still be bound by [RBAC](#configuration) for -> the policy to be considered for evaluation against a request. - -approver-policy supports selecting over the `issuerRef` and the `namespace` of a -request. - -At least either an `issuerRef` *or* `namespace` selector must be defined, even -if set to empty (`{}`). **Both** selectors must match on a CertificateRequest -for the request to evaluated by the policy if both are defined. - -#### `issuerRef` - -The `issuerRef` CertificateRequestPolicy selector selects on the corresponding -`issuerRef` stanza on the CertificateRequest. - -`issuerRef` values accept wildcards "\*". If an `issuerRef` is set to an empty -object `{}`, then the policy will match against _all_ requests. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: my-policy -spec: - ... - selector: - issuerRef: - name: "my-ca" - kind: "*Issuer" - group: "cert-manager.io" -``` - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: match-all-requests -spec: - ... - selector: - issuerRef: {} -``` - -#### `namespace` - -The `namespace` CertificateRequestPolicy selector selects on the Namespace to -which the CertificateRequest was created in. The selector can be defined with -either `matchNames` or `matchLabels`. - -`matchNames` takes a list of strings which match the _name_ of the Namespace. -Accepts wildcards "\*". - -`matchLabels` takes a list of key value strings which match on the labels of the -Namespace that the CertificateRequest was created in. Please see the [Kubernetes -documentation][] for more information on `matchLabels` behavior. - -If a `namespace` is set to an empty object `{}`, then the policy will match -against _all_ requests. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: my-policy -spec: - ... - selector: - namespace: - matchNames: - - "default" - - "app-team-*" - matchLabels: - foo: bar - team: dev -``` - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: match-all-requests -spec: - ... - selector: - namespace: {} -``` - -[Kubernetes documentation]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#resources-that-support-set-based-requirements - -### Plugins - -Plugins are external approvers that are built into approver-policy at compile -time. Plugins are designed to be used as extensions to the existing policy -checks where the user requires special functionality that the existing checks -can't provide. - -Plugins are defined as a block on the CertificateRequestPolicy `spec`. - -```yaml -apiVersion: policy.cert-manager.io/v1alpha1 -kind: CertificateRequestPolicy -metadata: - name: plugins -spec: - ... - plugins: - my-plugin: - values: - val-1: key-1 -``` - -## Known Plugins from the Community - -- [CEL approver-policy plugin](https://github.com/erikgb/cel-approver-policy-plugin) (experimental) - -If you want to implement an external approver policy plugin take a look at the -example implementation at -https://github.com/cert-manager/example-approver-policy-plugin. - -Have you implemented a plugin for approver-policy? Feel free to add a link to your plugin from this page by -opening a pull request in the [cert-manager website project](https://github.com/cert-manager/website). - -## API Reference - -> 📖 Read the [approver-policy API reference](api-reference.md). diff --git a/content/v1.13-docs/projects/approver-policy/api-reference.md b/content/v1.13-docs/projects/approver-policy/api-reference.md deleted file mode 100644 index e4d45bb829..0000000000 --- a/content/v1.13-docs/projects/approver-policy/api-reference.md +++ /dev/null @@ -1,978 +0,0 @@ ---- -title: approver-policy API Reference -description: "approver-policy API documentation" ---- - -Packages: - -- [`policy.cert-manager.io/v1alpha1`](#policycert-manageriov1alpha1) - -# `policy.cert-manager.io/v1alpha1` - -Resource Types: - - -- [CertificateRequestPolicy](#certificaterequestpolicy) - - - - -## `CertificateRequestPolicy` - - - - - -CertificateRequestPolicy is an object for describing a "policy profile" that makes decisions on whether applicable CertificateRequests should be approved or denied. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              apiVersionstringpolicy.cert-manager.io/v1alpha1true
              kindstringCertificateRequestPolicytrue
              metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
              specobject - CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy.
              -
              false
              statusobject - CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy.
              -
              false
              - - -### `CertificateRequestPolicy.spec` - - -CertificateRequestPolicySpec defines the desired state of CertificateRequestPolicy. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              selectorobject - Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation.
              -
              true
              allowedobject - Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible.
              -
              false
              constraintsobject - Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute.
              -
              false
              pluginsmap[string]object - Plugins define a set of plugins and their configuration that should be executed when this policy is evaluated against a CertificateRequest. A plugin must already be built within approver-policy for it to be available.
              -
              false
              - - -### `CertificateRequestPolicy.spec.selector` - - -Selector is used for selecting over which CertificateRequests this CertificateRequestPolicy is appropriate for and so will used for its approval evaluation. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              issuerRefobject - IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". - The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ```
              -
              false
              namespaceobject - Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected.
              -
              false
              - - -### `CertificateRequestPolicy.spec.selector.issuerRef` - - -IssuerRef is used to match this CertificateRequestPolicy against processed CertificateRequests. This policy will only be evaluated against a CertificateRequest whose `spec.issuerRef` field matches `spec.selector.issuerRef`. CertificateRequests will not be processed on unmatched `issuerRef` if defined, regardless of whether the requestor is bound by RBAC. Accepts wildcards "*". Omitted values are equivalent to "*". - The following value will match _all_ `issuerRefs`: ``` issuerRef: {} ``` - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              groupstring - Group is the wildcard selector to match the `spec.issuerRef.group` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
              -
              false
              kindstring - Kind is the wildcard selector to match the `spec.issuerRef.kind` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
              -
              false
              namestring - Name is the wildcard selector to match the `spec.issuerRef.name` field on requests. Accepts wildcards "*". An omitted field or value of `nil` matches all.
              -
              false
              - - -### `CertificateRequestPolicy.spec.selector.namespace` - - -Namespace is used to select on Namespaces, meaning the CertificateRequestPolicy will only match on CertificateRequests that have been created in matching selected Namespaces. If this field is omitted, all Namespaces are selected. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              matchLabelsmap[string]string - MatchLabels is the set of Namespace labels that select on CertificateRequests which have been created in a Namespace matching the selector.
              -
              false
              matchNames[]string - MatchNames are the set of Namespace names that select on CertificateRequests that have been created in a matching Namespace. Accepts wildcards "*".
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed` - - -Allowed is the set of attributes that are "allowed" by this policy. A CertificateRequest will only be considered permissible for this policy if the CertificateRequest has the same or less as what is allowed. Empty or `nil` allowed fields mean CertificateRequests are not allowed to have that field present to be permissible. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              commonNameobject - CommonName defines the X.509 Common Name that is permissible.
              -
              false
              dnsNamesobject - DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*".
              -
              false
              emailAddressesobject - EmailAddresses defines the X.509 Email SANs that may be requested for.
              -
              false
              ipAddressesobject - IPAddresses defines the X.509 IP SANs that may be requested for.
              -
              false
              isCAboolean - IsCA defines whether it is permissible for a CertificateRequest to have the `spec.IsCA` field set to `true`. An omitted field, value of `nil` or `false`, forbids the `spec.IsCA` field from bring `true`. A value of `true` permits CertificateRequests setting the `spec.IsCA` field to `true`.
              -
              false
              subjectobject - Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested.
              -
              false
              urisobject - URIs defines the X.509 URI SANs that may be requested for.
              -
              false
              usages[]enum - Usages defines the list of permissible key usages that may appear on the CertificateRequest `spec.keyUsages` field. An omitted field or value of `nil` forbids any Usages being requested. An empty slice `[]` is equivalent to `nil`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.commonName` - - -CommonName defines the X.509 Common Name that is permissible. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
              -
              false
              valuestring - Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.dnsNames` - - -DNSNames defines the X.509 DNS SANs that may be requested for. Accepts wildcards "*". - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.emailAddresses` - - -EmailAddresses defines the X.509 Email SANs that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.ipAddresses` - - -IPAddresses defines the X.509 IP SANs that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject` - - -Subject defines the X.509 subject that is permissible. An omitted field or value of `nil` forbids any Subject being requested. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              countriesobject - Countries define the X.509 Subject Countries that may be requested for.
              -
              false
              localitiesobject - Localities defines the X.509 Subject Localities that may be requested for.
              -
              false
              organizationalUnitsobject - OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for.
              -
              false
              organizationsobject - Organizations define the X.509 Subject Organizations that may be requested for.
              -
              false
              postalCodesobject - PostalCodes defines the X.509 Subject Postal Codes that may be requested for.
              -
              false
              provincesobject - Provinces defines the X.509 Subject Provinces that may be requested for.
              -
              false
              serialNumberobject - SerialNumber defines the X.509 Subject Serial Number that may be requested for.
              -
              false
              streetAddressesobject - StreetAddresses defines the X.509 Subject Street Addresses that may be requested for.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.countries` - - -Countries define the X.509 Subject Countries that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.localities` - - -Localities defines the X.509 Subject Localities that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.organizationalUnits` - - -OrganizationalUnits defines the X.509 Subject Organizational Units that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.organizations` - - -Organizations define the X.509 Subject Organizations that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.postalCodes` - - -PostalCodes defines the X.509 Subject Postal Codes that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.provinces` - - -Provinces defines the X.509 Subject Provinces that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.serialNumber` - - -SerialNumber defines the X.509 Subject Serial Number that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Value is also defined.
              -
              false
              valuestring - Value defines the value that is permissible to be present on the request. Accepts wildcards "*". An omitted field or value of `nil` forbids the value from being requested. An empty string is equivalent to `nil`, however an empty string pared with Required as `true` is an impossible condition that always denies. Value may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.subject.streetAddresses` - - -StreetAddresses defines the X.509 Subject Street Addresses that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.allowed.uris` - - -URIs defines the X.509 URI SANs that may be requested for. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              requiredboolean - Required marks this field as being a required value on the request. May only be set to true if Values is also defined. Default is nil which marks the field as not required.
              -
              false
              values[]string - Defines the values that are permissible to be present on request. Accepts wildcards "*". An omitted field or value of `nil` forbids any value on the related field in the request from being requested. An empty slice `[]` is equivalent to `nil`, however an empty slice pared with Required `true` is an impossible condition that always denies. Values may not be `nil` if Required is `true`.
              -
              false
              - - -### `CertificateRequestPolicy.spec.constraints` - - -Constraints is the set of attributes that _must_ be satisfied by the CertificateRequest for the request to be permissible by the policy. Empty or `nil` constraint fields mean CertificateRequests satisfy that field with any value of their corresponding attribute. - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              maxDurationstring - MaxDuration defines the maximum duration a certificate may be requested for. Values are inclusive (i.e. a max value of `1h` will accept a duration of `1h`). MaxDuration and MinDuration may be the same value. An omitted field or value of `nil` permits any maximum duration. If MaxDuration is defined, a duration _must_ be requested on the CertificateRequest.
              -
              false
              minDurationstring - MinDuration defines the minimum duration a certificate may be requested for. Values are inclusive (i.e. a min value of `1h` will accept a duration of `1h`). MinDuration and MaxDuration may be the same value. An omitted field or value of `nil` permits any minimum duration. If MinDuration is defined, a duration _must_ be requested on the CertificateRequest.
              -
              false
              privateKeyobject - PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor.
              -
              false
              - - -### `CertificateRequestPolicy.spec.constraints.privateKey` - - -PrivateKey defines the shape of permissible private keys that may be used for the request with this policy. An omitted field or value of `nil` permits the use of any private key by the requestor. - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              algorithmenum - Algorithm defines the allowed crypto algorithm that is used by the requestor for their private key in their request. An omitted field or value of `nil` permits any Algorithm.
              -
              - Enum: RSA, ECDSA, Ed25519
              -
              false
              maxSizeinteger - MaxSize defines the maximum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MaxSize and MinSize may be the same value. An omitted field or value of `nil` permits any maximum size.
              -
              false
              minSizeinteger - MinSize defines the minimum key size a requestor may use for their private key. Values are inclusive (i.e. a min value of `2048` will accept a size of `2048`). MinSize and MaxSize may be the same value. An omitted field or value of `nil` permits any minimum size.
              -
              false
              - - -### `CertificateRequestPolicy.spec.plugins[key]` - - -CertificateRequestPolicyPluginData is configuration needed by the plugin approver to evaluate a CertificateRequest on this policy. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              valuesmap[string]string - Values define a set of well-known, to the plugin, key value pairs that are required for the plugin to successfully evaluate a request based on this policy.
              -
              false
              - - -### `CertificateRequestPolicy.status` - - -CertificateRequestPolicyStatus defines the observed state of the CertificateRequestPolicy. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              conditions[]object - List of status conditions to indicate the status of the CertificateRequestPolicy. Known condition types are `Ready`.
              -
              false
              - - -### `CertificateRequestPolicy.status.conditions[index]` - - -CertificateRequestPolicyCondition contains condition information for a CertificateRequestPolicyStatus. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              statusstring - Status of the condition, one of ('True', 'False', 'Unknown').
              -
              true
              typestring - Type of the condition, known values are (`Ready`).
              -
              true
              lastTransitionTimestring - LastTransitionTime is the timestamp corresponding to the last status change of this condition.
              -
              - Format: date-time
              -
              false
              messagestring - Message is a human readable description of the details of the last transition, complementing reason.
              -
              false
              observedGenerationinteger - If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the CertificateRequestPolicy.
              -
              - Format: int64
              -
              false
              reasonstring - Reason is a brief machine readable explanation for the condition's last transition.
              -
              false
              diff --git a/content/v1.13-docs/projects/csi-driver-spiffe.md b/content/v1.13-docs/projects/csi-driver-spiffe.md deleted file mode 100644 index 771ce6a1ae..0000000000 --- a/content/v1.13-docs/projects/csi-driver-spiffe.md +++ /dev/null @@ -1,257 +0,0 @@ ---- -title: csi-driver-spiffe -description: 'Container Storage Interface (CSI) driver plugin for Kubernetes, providing SPIFFE SVIDs using cert-manager' ---- - -csi-driver-spiffe is a Container Storage Interface (CSI) driver plugin for -Kubernetes, designed to work alongside [cert-manager](https://cert-manager.io/). - -It transparently delivers [SPIFFE](https://spiffe.io/) [SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-verifiable-identity-document-svid) -(in the form of X.509 certificate key pairs) to mounting Kubernetes Pods. - -The end result is that any and all Pods running in Kubernetes can securely request -a SPIFFE identity document from a Trust Domain with minimal configuration. - -These documents in turn have the following properties: - -- automatically renewed ✔️ -- private key never leaves the node's virtual memory ✔️ -- each Pod's document is unique ✔️ -- the document shares the same life cycle as the Pod and is destroyed on Pod termination ✔️ - -```yaml -... - volumeMounts: - - mountPath: "/var/run/secrets/spiffe.io" - name: spiffe - volumes: - - name: spiffe - csi: - driver: spiffe.csi.cert-manager.io - readOnly: true -``` - -SPIFFE documents can then be used by Pods for mutual TLS (mTLS) or other authentication within their Trust Domain. -### Components - -The project is split into two components. - -#### CSI Driver - -The CSI driver runs as DaemonSet on the cluster which is responsible for -generating, requesting, and mounting the certificate key pair to Pods on the -node it manages. The CSI driver creates and manages a -[tmpfs](https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html) directory -which is used to create and mount Pod volumes from. - -When a Pod is created with the CSI volume configured, the -driver will locally generate a private key, and create a cert-manager -[CertificateRequest](../concepts/certificaterequest.md) -in the same Namespace as the Pod. - -The driver uses [CSI Token Request](https://kubernetes-csi.github.io/docs/token-requests.html) to both -discover the Pod's identity to form the SPIFFE identity contained in the X.509 -certificate signing request, as well as securely impersonate its ServiceAccount -when creating the CertificateRequest. - -Once signed by the pre-configured target signer, the driver will mount the -private key and signed certificate into the Pod's Volume to be made available as -a Volume Mount. This certificate key pair is regularly renewed based on the -expiry of the signed certificate. - -#### Approver - -A distinct [cert-manager approver](../concepts/certificaterequest.md#approval) -Deployment is responsible for managing the approval and denial condition of -created CertificateRequests that target the configured SPIFFE Trust Domain -signer. - -The approver ensures that requests have: - -1. acceptable key usages (Key Encipherment, Digital Signature, Client Auth, Server Auth); -2. a requested duration which matches the enforced duration (default 1 hour); -3. no [SANs](https://en.wikipedia.org/wiki/Subject_Alternative_Name) or other - identifiable attributes except a single [URI SAN](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier); -4. a URI SAN which is the SPIFFE identity of the ServiceAccount which created - the CertificateRequest; -5. a SPIFFE ID Trust Domain matching the one that was configured at startup. - -If any of these checks do not pass, the CertificateRequest will be marked as -Denied, else it will be marked as Approved. The approver will only manage -CertificateRequests who request from the same [IssuerRef](../concepts/certificaterequest.md) -that has been configured. - -## Installation - -### Requirements - -csi-driver-spiffe generally requires Kubernetes version `v1.21` or newer. - -If running on Kubernetes `v1.20`, you'll need the `--feature-gates=CSIServiceAccountToken=true` flag. - -cert-manager `v1.3` or higher is also required. - -### Steps - -#### 1. Install cert-manager - -csi-driver-spiffe requires cert-manager to be [installed](../installation/README.md) but -a default installation of cert-manager **will not work**. - -> ⚠️ It is **vital** that the [default approver is disabled in cert-manager](../concepts/certificaterequest.md#approver-controller) ⚠️ - -If the default approver is not disabled, the csi-driver-spiffe approver will -race with cert-manager and policy enforcement will become useless. - -```bash -helm repo add jetstack https://charts.jetstack.io --force-update - -# NOTE: This isn't the usual cert-manager install process; -# we're disabling the cert-manager approver. -# See explanation above! - -helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager \ - --set extraArgs={--controllers='*\,-certificaterequests-approver'} \ - --set installCRDs=true \ - --create-namespace -``` - -#### 2. Configure an Issuer / ClusterIssuer - -Install or configure a [ClusterIssuer](../configuration/README.md) to give -cert-manager the ability to sign against your Trust Domain. - -If you want a namespace-scoped Issuer, then it must be created in every namespace -that Pods will mount volumes from. - -You must use an Issuer type which is compatible with signing URI SAN certificates; -ACME issuers won't generally work, and the SelfSigned issuer is not appropriate. - -An example demo [ClusterIssuer](../concepts/issuer.md#namespaces) can -be found [in the csi-driver-spiffe repo](https://github.com/cert-manager/csi-driver-spiffe/blob/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/clusterissuer.yaml). - -> ⚠️ This Trust Domain's root CA is generated by cert-manager and **the private key is stored in the cluster** -> This might not be appropriate for production deployments! - -We'll also use [cmctl](../reference/cmctl.md) to approve the CertificateRequest, -since the default approver was disabled above. - -```terminal -kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/clusterissuer.yaml - -# We must also approve the CertificateRequest since we -# disabled the default approver -cmctl approve -n cert-manager \ - $(kubectl get cr -n cert-manager -ojsonpath='{.items[0].metadata.name}') -``` - -#### 3. Install csi-driver-spiffe - -Install csi-driver-spiffe into the cluster using the issuer we configured. We -must also configure the issuer resource type and name of the issuer we -configured so that the approver has [permissions to approve referencing CertificateRequests](../concepts/certificaterequest.md#rbac-syntax). - -Note that the `issuer.name`, `issuer.kind` and `issuer.group` will need to be changed to match -the issuer you're actually using! - -```bash -helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \ - --set "app.logLevel=1" \ - --set "app.trustDomain=my.trust.domain" \ - --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \ - \ - --set "app.issuer.name=csi-driver-spiffe-ca" \ - --set "app.issuer.kind=ClusterIssuer" \ - --set "app.issuer.group=cert-manager.io" -``` - -## Usage - -Once the driver is successfully installed, Pods can begin to request and mount -their key and SPIFFE certificate. Since the Pod's ServiceAccount is impersonated -when creating CertificateRequests, every ServiceAccount must be given that -permission which intends to use the volume. - -Example manifest with a dummy Deployment: - -```bash -kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/example-app.yaml - -kubectl exec -n sandbox \ - $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') \ - -- \ - cat /var/run/secrets/spiffe.io/tls.crt | \ - openssl x509 --noout --text | \ - grep "Issuer:" -# expected output: Issuer: CN = csi-driver-spiffe-ca - -kubectl exec -n sandbox \ - $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') \ - -- \ - cat /var/run/secrets/spiffe.io/tls.crt | \ - openssl x509 --noout --text | \ - grep "URI:" -# expected output: URI:spiffe://foo.bar/ns/sandbox/sa/example-app -``` - -### FS-Group - -When running Pods with a specified user or group, the volume will not be -readable by default due to Unix based file system permissions. The mounting -volumes file group can be specified using the following volume attribute: - -```yaml -... - securityContext: - runAsUser: 123 - runAsGroup: 456 - volumes: - - name: spiffe - csi: - driver: spiffe.csi.cert-manager.io - readOnly: true - volumeAttributes: - spiffe.csi.cert-manager.io/fs-group: "456" -``` - -```bash -kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/23a9fe31b9879fb162cb24c98352d4d5019171f2/deploy/example/fs-group-app.yaml - -kubectl exec -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app-fs-group -o jsonpath='{.items[0].metadata.name}') -- cat /var/run/secrets/spiffe.io/tls.crt | openssl x509 --noout --text | grep URI: -# expected output: URI:spiffe://foo.bar/ns/sandbox/sa/fs-group-app -``` - -### Root CA Bundle - -By default, the CSI driver will only mount the Pod's private key and signed -certificate. csi-driver-spiffe can be optionally configured to also mount a -statically defined CA bundle from a volume that will be written to all Pod -volumes. - -If the CSI driver detects this bundle has changed (through overwrite, renewal, -etc), the new bundle will be written to all existing volumes. - -The following example mounts the CA certificate used by the Trust Domain -ClusterIssuer. - -```terminal -helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \ - --set "app.logLevel=1" \ - --set "app.trustDomain=my.trust.domain" \ - --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \ - \ - --set "app.issuer.name=csi-driver-spiffe-ca" \ - --set "app.issuer.kind=ClusterIssuer" \ - --set "app.issuer.group=cert-manager.io" \ - \ - --set "app.driver.volumes[0].name=root-cas" \ - --set "app.driver.volumes[0].secret.secretName=csi-driver-spiffe-ca" \ - --set "app.driver.volumeMounts[0].name=root-cas" \ - --set "app.driver.volumeMounts[0].mountPath=/var/run/secrets/cert-manager-csi-driver-spiffe" \ - --set "app.driver.sourceCABundle=/var/run/secrets/cert-manager-csi-driver-spiffe/ca.crt" - -kubectl rollout restart deployment -n sandbox my-csi-app - -kubectl exec -it -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') -- ls /var/run/secrets/spiffe.io/ -# expected output: ca.crt tls.crt tls.key -``` diff --git a/content/v1.13-docs/projects/csi-driver.md b/content/v1.13-docs/projects/csi-driver.md deleted file mode 100644 index 31f7f1c74d..0000000000 --- a/content/v1.13-docs/projects/csi-driver.md +++ /dev/null @@ -1,231 +0,0 @@ ---- -title: csi-driver -description: '' ---- - -csi-driver is a Container Storage Interface (CSI) driver plugin for Kubernetes -to work along cert-manager. The goal for this plugin is to seamlessly request -and mount certificate key pairs to pods. This is useful for facilitating mTLS, -or otherwise securing connections of pods with guaranteed present certificates -whilst having all of the features that cert-manager provides. - -## Why a CSI Driver? - -- Ensure private keys never leave the node and are never sent over the network. - All private keys are stored locally on the node. -- Unique key and certificate per application replica with a grantee to be - present on application run time. -- Reduce resource management overhead by defining certificate request spec - in-line of the Kubernetes Pod template. -- Automatic renewal of certificates based on expiry of each individual - certificate. -- Keys and certificates are destroyed during application termination. -- Scope for extending plugin behavior with visibility on each replica's - certificate request and termination. - -## Requirements and Installation - -This CSI driver plugin makes use of the 'CSI inline volume' feature - Alpha as -of `v1.15` and beta in `v1.16`. Kubernetes versions `v1.16` and higher require -no extra configuration however `v1.15` requires the following feature gate set: -``` ---feature-gates=CSIInlineVolume=true -``` - -You must have a working installation of cert-manager present on the cluster. -Instructions on how to install cert-manager can be found -[on cert-manager.io](../installation/README.md). - -To install the csi-driver, use helm install: - -```terminal -helm repo add jetstack https://charts.jetstack.io --force-update -helm upgrade -i -n cert-manager cert-manager-csi-driver jetstack/cert-manager-csi-driver --wait -``` - -Or apply the static manifests to your cluster: - -```terminal -helm repo add jetstack https://charts.jetstack.io --force-update -helm template jetstack/cert-manager-csi-driver | kubectl apply -n cert-manager -f - -``` - - -You can verify the installation has completed correctly by checking the presence -of the CSIDriver resource as well as a CSINode resource present for each node, -referencing `csi.cert-manager.io`. - -``` -$ kubectl get csidrivers -NAME CREATED AT -csi.cert-manager.io 2019-09-06T16:55:19Z - -$ kubectl get csinodes -o yaml -apiVersion: v1 -items: -- apiVersion: storage.k8s.io/v1beta1 - kind: CSINode - metadata: - name: kind-control-plane - ownerReferences: - - apiVersion: v1 - kind: Node - name: kind-control-plane -... - spec: - drivers: - - name: csi.cert-manager.io - nodeID: kind-control-plane - topologyKeys: null -... -``` - -The CSI driver is now installed and is ready to be used for pods in the cluster. - -## Requesting and Mounting Certificates - -To request certificates from cert-manager, simply define a volume mount where -the key and certificate will be written to, along with a volume with attributes -that define the cert-manager request. The following is a dummy app that mounts a -key certificate pair to `/tls` and has been signed by the `ca-issuer` with a DNS -name valid for `my-service.sandbox.svc.cluster.local`. - -``` -apiVersion: v1 -kind: Pod -metadata: - name: my-csi-app - namespace: sandbox - labels: - app: my-csi-app -spec: - containers: - - name: my-frontend - image: busybox - volumeMounts: - - mountPath: "/tls" - name: tls - command: [ "sleep", "1000000" ] - volumes: - - name: tls - csi: - driver: csi.cert-manager.io - volumeAttributes: - csi.cert-manager.io/issuer-name: ca-issuer - csi.cert-manager.io/dns-names: ${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local -``` - -Once created, the CSI driver will generate a private key locally, request a -certificate from cert-manager based on the given attributes, then store both -locally to be mounted to the pod. The pod will remain in a pending state until -this process has been completed. - -For more information on how to set up issuers for your cluster, refer to the -cert-manager documentation -[here](../configuration/README.md). **Note** it is not -possible to use `SelfSigned` Issuers with the CSI Driver. In order for -cert-manager to self sign a certificate, it needs access to the secret -containing the private key that signed the certificate request to sign the end -certificate. This secret is not used and so not available in the CSI driver use -case. - -## Supported Volume Attributes - -The csi-driver driver aims to have complete feature parity with all possible -values available through the cert-manager API however currently supports the -following values; - -| Attribute | Description | Default | Example | -|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|----------------------------------| -| `csi.cert-manager.io/issuer-name` | The Issuer name to sign the certificate request. | | `ca-issuer` | -| `csi.cert-manager.io/issuer-kind` | The Issuer kind to sign the certificate request. | `Issuer` | `ClusterIssuer` | -| `csi.cert-manager.io/issuer-group` | The group name the Issuer belongs to. | `cert-manager.io` | `out.of.tree.foo` | -| `csi.cert-manager.io/common-name` | Certificate common name (supports variables). | | `my-cert.foo` | -| `csi.cert-manager.io/dns-names` | DNS names the certificate will be requested for. At least a DNS Name, IP or URI name must be present (supports variables). | | `a.b.foo.com,c.d.foo.com` | -| `csi.cert-manager.io/ip-sans` | IP addresses the certificate will be requested for. | | `192.0.0.1,192.0.0.2` | -| `csi.cert-manager.io/uri-sans` | URI names the certificate will be requested for (supports variables). | | `spiffe://foo.bar.cluster.local` | -| `csi.cert-manager.io/duration` | Requested duration the signed certificate will be valid for. | `720h` | `1880h` | -| `csi.cert-manager.io/is-ca` | Mark the certificate as a certificate authority. | `false` | `true` | -| `csi.cert-manager.io/key-usages` | Set the key usages on the certificate request. | `digital signature,key encipherment` | `server auth,client auth` | -| `csi.cert-manager.io/key-encoding` | Set the key encoding format (PKCS1 or PKCS8). | `PKCS1` | `PKCS8` | -| `csi.cert-manager.io/certificate-file` | File name to store the certificate file at. | `tls.crt` | `foo.crt` | -| `csi.cert-manager.io/ca-file` | File name to store the ca certificate file at. | `ca.crt` | `foo.ca` | -| `csi.cert-manager.io/privatekey-file` | File name to store the key file at. | `tls.key` | `foo.key` | -| `csi.cert-manager.io/fs-group` | Set the FS Group of written files. Should be paired with and match the value of the consuming container `runAsGroup`. | | `2000` | -| `csi.cert-manager.io/renew-before` | The time to renew the certificate before expiry. Defaults to a third of the requested duration. | `$CERT_DURATION/3` | `72h` | -| `csi.cert-manager.io/reuse-private-key` | Re-use the same private when when renewing certificates. | `false` | `true` | -| `csi.cert-manager.io/pkcs12-enable` | Enable writing the signed certificate chain and private key as a PKCS12 file. | | `true` | -| `csi.cert-manager.io/pkcs12-filename` | File location to write the PKCS12 file. Requires `csi.cert-manager.io/keystore-pkcs12-enable` be set to `true`. | `keystore.p12` | `tls.p12` | -| `csi.cert-manager.io/pkcs12-password` | Password used to encode the PKCS12 file. Required when PKCS12 is enabled (`csi.cert-manager.io/keystore-pkcs12-enable: true`). | | `my-password` | - -### Variables - -The following attributes support variables that are evaluated when a request is -made for the mounting Pod. These variables are useful for constructing requests -with SANs that contain values from the mounting Pod. - -``` -`csi.cert-manager.io/common-name` -`csi.cert-manager.io/dns-names` -`csi.cert-manager.io/uri-sans` -``` - -Variables follow the [go `os.Expand`](https://pkg.go.dev/os#Expand) structure, -which is generally what you would expect on a UNIX shell. The CSI driver has -access to the following variables: - -``` -${POD_NAME} -${POD_NAMESPACE} -${POD_UID} -${SERVICE_ACCOUNT_NAME} -``` - -#### Example Usage - -```yaml -volumeAttributes: - csi.cert-manager.io/issuer-name: ca-issuer - csi.cert-manager.io/dns-names: "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local" - csi.cert-manager.io/uri-sans: "spiffe://cluster.local/ns/${POD_NAMESPACE}/pod/${POD_NAME}/${POD_UID}" - csi.cert-manager.io/common-name: "${SERVICE_ACCOUNT_NAME}.${POD_NAMESPACE}" -``` - -## Requesting Certificates using the mounting Pod's ServiceAccount - -If the flag `--use-token-request` is enabled on the csi-driver DaemonSet, the -[CertificateRequest](../concepts/certificaterequest.md) resource will be created -by the mounting Pod's ServiceAccount. This can be pared with -[approver-policy](./approver-policy/README.md) to enable advanced policy on a per -ServiceAccount basis. - -Ensure to give permissions to Pod ServiceAccounts to create CertificateRequests -with this flag enabled, i.e: - -```yaml -# WARNING: This RBAC will enable any identiy in the cluster to create -# CertificateRequests. This may or may not be problimatic based on your security -# model. It is likely worth scoping the set of identities in the -# `ClusterRoleBinding` `subjects` stanza. -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: cert-manager-csi-driver-all-cr-create -rules: -- apiGroups: ["cert-manager.io"] - resources: ["certificaterequests"] - verbs: [ "create" ] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: cert-manager-csi-driver-all-cr-create -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cert-manager-csi-driver-all-cr-create -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: system:authenticated -``` diff --git a/content/v1.13-docs/projects/istio-csr.md b/content/v1.13-docs/projects/istio-csr.md deleted file mode 100644 index 57748517bc..0000000000 --- a/content/v1.13-docs/projects/istio-csr.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: istio-csr -description: '' ---- - -istio-csr is an agent that allows for [Istio](https://istio.io) workload and -control plane components to be secured using -[cert-manager](https://cert-manager.io). - -Certificates facilitating mTLS — both inter -and intra-cluster — will be signed, delivered and renewed using [cert-manager -issuers](https://cert-manager.io/docs/concepts/issuer). - -## Getting Started Guide For istio-csr - -We have [a guide](../tutorials/istio-csr/istio-csr.md) for setting up istio-csr in a fresh -[kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) cluster. - -Following the guide is the best way to see istio-csr in action. - -If you've already seen istio-csr in action or if you're experienced with running -Istio and just want quick installation instructions, read on for more details. - -## Lower-Level Details (For Experienced Istio Users) - -⚠️ The [getting started](../tutorials/istio-csr/istio-csr.md) guide is a better place if you just want to try istio-csr out! - -Running istio-csr requires a few steps and preconditions in order: - -1. A cluster _without_ Istio already installed -2. cert-manager [installed](https://cert-manager.io/docs/installation/) in the cluster -3. An `Issuer` or `ClusterIssuer` which will be used to issue Istio certificates -4. istio-csr installed (likely via helm) -5. Istio [installed](https://istio.io/latest/docs/setup/install/istioctl/) with - some custom config required, e.g. using the example config from the [repository](https://github.com/cert-manager/istio-csr/tree/main/hack). - -### Why Custom Istio Install Manifests? - -If you take a look at the contents of [the example Istio install -manifests](https://github.com/cert-manager/istio-csr/tree/main/hack) -there are a few custom configuration options which are important. - -Required changes include setting `ENABLE_CA_SERVER` to `false` and setting the `caAddress` from which Istio will -request certificates; replacing the CA server is the whole point of istio-csr! - -Mounting and statically specifying the root CA is also an important recommended step. Without a manually specified -root CA istio-csr defaults to trying to discover root CAs automatically, which could theoretically lead to a -[signer hijacking attack](https://github.com/cert-manager/istio-csr/issues/103#issuecomment-923882792) if for example -a signer's token was stolen (such as the cert-manager controller's token). - -### Issuer or ClusterIssuer? - -Unless you know you need a `ClusterIssuer` we'd recommend starting with an `Issuer`, since it should be easier to reason about -the access controls for an Issuer; they're namespaced and so naturally a little more limited in scope. - -That said, if you view your entire Kubernetes cluster as being a trust domain itself, then a ClusterIssuer is the more natural -fit. The best choice will depend on your specific situation. - -Our [getting started guide](../tutorials/istio-csr/istio-csr.md) uses an `Issuer`. - -### Which Issuer Type? - -Whether you choose to use an `Issuer` or a `ClusterIssuer`, you'll also need to choose the type of issuer you want such as: - -- [CA](https://cert-manager.io/docs/configuration/ca/) -- [Vault](https://cert-manager.io/docs/configuration/vault/) -- or an [external issuer](https://cert-manager.io/docs/configuration/external/) - -The key requirement is that arbitrary values can be placed into the `subjectAltName` (SAN) X.509 extension, since -Istio places SPIFFE IDs there. - -That means that the ACME issuer **will not work** — publicly trusted certificates such as those issued by Let's Encrypt -don't allow arbitrary entries in the SAN, for very good reasons. - -If you're already using [HashiCorp Vault](https://www.vaultproject.io/) then the Vault issuer is an obvious choice. If -you want to control your own PKI entirely, we'd recommend the CA issuer. The choice is ultimately yours. - -### Installing istio-csr After Istio - -This is unsupported because it's exceptionally difficult to do safely. It's likely that installing istio-csr _after_ Istio isn't -possible to do without downtime, since installing istio-csr second would require a time period where all Istio sidecars trust -both the old Istio-managed CA and the new cert-manager controlled CA. - -## How Does istio-csr Work? - -istio-csr implements the gRPC Istio certificate service which authenticates, -authorizes, and signs incoming certificate signing requests from Istio -workloads, routing all certificate handling through cert-manager installed in -the cluster. - -This seamlessly matches the behavior of istiod in a typical installation, while -allowing certificate management through cert-manager. diff --git a/content/v1.13-docs/projects/trust-manager/README.md b/content/v1.13-docs/projects/trust-manager/README.md deleted file mode 100644 index 7507585c4b..0000000000 --- a/content/v1.13-docs/projects/trust-manager/README.md +++ /dev/null @@ -1,417 +0,0 @@ ---- -title: trust-manager -description: 'Distributing Trust Bundles in Kubernetes' ---- - -trust-manager is the easiest way to manage trust bundles in Kubernetes and OpenShift clusters. - -It orchestrates bundles of trusted X.509 certificates which are primarily used for validating -certificates during a TLS handshake but can be used in other situations, too. - -## Overview - -trust-manager is a small Kubernetes operator which aims to help reduce the overhead of managing -TLS trust bundles in your clusters. - -It adds the `Bundle` custom Kubernetes resource (CRD) which can read input from various sources -and combine the resultant certificates into a bundle ready to be used by your applications. - -trust-manager ensures that it's both quick and easy to keep your trusted certificates up-to-date -and enables cluster administrators to easily automate providing a secure bundle without having -to worry about rebuilding containers to update trust stores. - -It's designed to complement cert-manager and works well when consuming CA certificates from a -cert-manager `Issuer` or `ClusterIssuer` but can be used entirely independently from cert-manager -if needed. - -## Usage - -trust-manager is intentionally simple, adding just one new Kubernetes `CustomResourceDefintion`: `Bundle`. - -A `Bundle` represents a set of X.509 certificates that should be distributed across a cluster. - -All `Bundle`s are cluster scoped. - -`Bundle`s comprise a list of `sources` from which trust-manager will assemble the final bundle, along with -a `target` describing how and where the resulting bundle will be written. - -An example `Bundle` might look like this: - -```yaml -apiVersion: trust.cert-manager.io/v1alpha1 -kind: Bundle -metadata: - name: my-org.com # The bundle name will also be used for the target -spec: - sources: - # Include a bundle of publicly trusted certificates which can be - # used to validate most TLS certificates on the internet, such as - # those issued by Let's Encrypt, Google, Amazon and others. - - useDefaultCAs: true - - # A Secret in the "trust" namespace; see "Trust Namespace" below for further details - - secret: - name: "my-db-tls" - key: "ca.crt" - - # A ConfigMap in the "trust" namespace; see "Trust Namespace" below for further details - - configMap: - name: "my-org.net" - key: "root-certs.pem" - - # A manually specified string - - inLine: | - -----BEGIN CERTIFICATE----- - MIIC5zCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl - .... - 0V3NCaQrXoh+3xrXgX/vMdijYLUSo/YPEWmo - -----END CERTIFICATE----- - target: - # Sync the bundle to a ConfigMap called `my-org.com` in every namespace which - # has the label "linkerd.io/inject=enabled" - # All ConfigMaps will include a PEM-formatted bundle, here named "root-certs.pem" - # and in this case we also request a binary JKS formatted bundle, here named "bundle.jks" - configMap: - key: "root-certs.pem" - additionalFormats: - jks: - key: "bundle.jks" - namespaceSelector: - matchLabels: - linkerd.io/inject: "enabled" -``` - -`Bundle` resources currently support several source types: - -- `configMap` - a `ConfigMap` resource in the trust-manager namespace -- `secret` - a `Secret` resource in the trust-manager namespace -- `inLine` - a manually specified string containing at least one certificate -- `useDefaultCAs` - usually, a bundle of publicly trusted certificates - -These sources, along with the single currently supported target type (`configMap`) -are documented in the trust-manager [API reference documentation](./api-reference.md). - -#### Targets - -All `Bundle` targets are written to `ConfigMap`s whose name matches that of the `Bundle`, and every -target has a PEM-formatted bundle included. - -Users can also optionally - as of trust-manager v0.5.0 - choose to write a JKS formatted binary -bundle to the target. We understand that most Java applications tend to require a password on JKS -files (even though trust bundles don't contain secrets), so all trust-manager JKS bundles use the -default password `changeit`. - -#### Namespace Selector - -A target's `namespaceSelector` is used to restrict which Namespaces your `Bundle`'s target -should be synced to. - -`namespaceSelector` supports the field `matchLabels`. - -Please see [Kubernetes documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) -for more information about how label selectors can be configured. - -If `namespaceSelector` is empty, a `Bundle`'s target will be synced to all Namespaces. - -> ⚠️ A future update to trust-manager **will** change this behavior so that an empty namespace selector will sync only -to the trust-manager namespace by default. - -## Installation - -### Helm - -Helm is the easiest way to install trust-manager and comes with a publicly trusted certificate bundle package -(for the`useDefaultCAs` source) derived from Debian containers. - -When installed via Helm, trust-manager has a dependency on cert-manager for provisioning an application certificate, -and as such trust-manager is also installed into the cert-manager namespace. - -```bash -helm repo add jetstack https://charts.jetstack.io --force-update -helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set installCRDs=true --wait --create-namespace -helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --wait -``` - -### approver-policy Integration - -If you're running [approver-policy](../approver-policy/README.md) then cert-manager's default approver will be disabled which will mean that -trust-manager's webhook certificate will - by default - block when you install the Helm chart until it's manually approved. - -As of trust-manager v0.6.0 you can choose to automatically add an approver-policy `CertificateRequestPolicy` which -will approve the trust-manager webhook certificate: - -```bash -helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --set app.webhook.tls.approverPolicy.enabled=true --set app.webhook.tls.approverPolicy.certManagerNamespace=cert-manager --wait -``` - -Note that if you've installed cert-manager to a different namespace, you'll need to pass that namespace in `app.webhook.tls.approverPolicy.certManagerNamespace`! - -### Manual Installation - -We strongly recommend that you install trust-manager using Helm and we don't currently support manually installed -versions of trust-manager. This is so that we can focus on continuing to improve trust-manager with the resources -we currently have available. - -### Trust Namespace - -One of the more important configuration options you might need to consider at install time is which "trust namespace" to use, -which can be set via the Helm value `app.trust.namespace`. - -The trust namespace is the only one in which `Secret` and `ConfigMap` sources can be read. This restriction is in place -for security reasons - we don't want to give trust-manager the permission to read all `Secret`s or `ConfigMap`s in all namespaces. - -The trust namespace defaults to `cert-manager`, but there's no need for it to be set to the namespace that cert-manager -is installed in - trust-manager has no runtime dependency on cert-manager at all! - so we'd recommend setting the trust -namespace to whichever is most appropriate for your environment. - -An ideal deployment would be a fresh namespace dedicated entirely to trust-manager, to minimize the number of actors in your -cluster that can modify your trust sources. - -## Quick Start Example - -Let's get started with an example of creating our own `Bundle`! - -First we'll create a demo cluster: - -```bash -git clone https://github.com/cert-manager/trust-manager trust-manager -cd trust-manager -make demo -``` - -Once we have a running cluster, we can create a `Bundle` using the default CAs which were configured -when trust-manager started up. Since we've installed trust-manager using Helm, our default CA package -contains publicly trusted certificates derived from a Debian container. - -```bash -kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - < 🤔 Wondering why we used `tls.crt` and not `ca.crt`? More details [below](./README.md#preparing-for-production). - -Finally, we'll update our `Bundle` to include our new private CA: - -```bash -kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - < ⚠️ This upgrade process assumes that it's the only thing running. If another user or process changes Helm values -> while you're doing this process, you might overwrite their work. - -First, we'll dump our current Helm values, so we don't lose them: - -```bash -helm get values -n cert-manager trust-manager -oyaml > values.yaml -``` - -Next, if `defaultPackageImage.tag` is already set in `values.yaml`, update it. Otherwise, add it. -You can find the available tags [on `quay.io`](https://quay.io/repository/jetstack/cert-manager-package-debian?tab=tags&tag=latest). - -```yaml -# values.yaml -... -defaultPackageImage: - tag: XYZ -``` - -These versions of the default package image tags are derived directly from the version of the `ca-certificates` package in Debian. - -Finally, apply back the changes, being sure to manually specify the version of trust-manager which is installed, to avoid -also updating the trust-manager controller at the same as the default CA package: - -```bash -# Get the currently installed version. You could do this manually if you find that easier. -TRUST_MANAGER_VER=$(helm list --filter "^trust-manager$" -n cert-manager -ojson | jq -r ".[0].app_version") - -# Check the version makes sense -echo $TRUST_MANAGER_VER - -# Run the upgrade -helm upgrade -f values.yaml -n cert-manager trust-manager jetstack/trust-manager --version $TRUST_MANAGER_VER -``` - -If an incorrect tag is used, your deployment will fail and you'll likely need to use `helm rollback` to get back -to a working state. - -## Preparing for Production - -TLS can be complicated and there are many ways to misuse TLS certificates. - -Here are some potential gotchas here to be aware of before running trust-manager in production. - -If you're planning on running trust-manager in production and you're using more than just the default CA package, -we **strongly** advise you to read and understand this section. It could save you from causing an outage later. - -> ℹ️ These gotchas aren't specific to trust-manager and you could run into any of them with any method of managing TLS trust! - -### Bundling Intermediates - -If you've ever used a Let's Encrypt client such as [Certbot](https://certbot.eff.org/) you'll probably have -seen that it generates several certificate files, such as `cert.pem`, `chain.pem`, and `fullchain.pem`. - -These various files are provided to support various different applications, which might require the certificate -and the chain to be given separately. For most users and applications `fullchain.pem` is the only correct choice. - -Unfortunately the existence of these files has the unfortunate side effect of people sometimes assuming that `cert.pem` -is the correct choice even when `fullchain.pem` would be correct. This means that the rest of the chain will not -be sent when the certificate is used. - -Often, a quick fix that _seems_ to work for this is that clients add the chain to their trust store, which will seem -to fix certificate errors in the short term. It's easy for this kind of "fix" to end up being embedded somewhere as a -solution which others can follow. - -This "fix" is dangerous; it means that the intermediate cannot be safely rotated without all trust stores -which contain it being updated first. - -Intermediates in this case become _de facto_ root certificates, which completely defeats the point of having -intermediate certificates in the first place. - -Avoid using intermediates in any trust store wherever possible unless you're absolutely certain they should be included. -An example of where it might be OK would be cross signing, which is not likely to be required in the general case. - -It would be better to copy just the root certificate to a new `ConfigMap` and use that as a source rather than trusting -an intermediate. - -### cert-manager Integration: `ca.crt` vs `tls.crt` - -If you're pointing trust-manager at a `Secret` containing a cert-manager-issued certificate, you'll see two relevant -fields: `ca.crt` and `tls.crt`. (We're ignoring `tls.key` - trust-manager definitely doesn't need to access that) - -That leads to an obvious question: between `ca.crt` and `tls.crt`, which should I use for trust-manager? - -Unfortunately, it's impossible to say in the general case which field is correct to use, but we can provide guidelines. - -`tls.crt` will generally contain multiple certificates which may not all be issuers and some of which are likely to be -intermediate certificates. If that's the case, you shouldn't use `tls.crt` as a source. (See "Bundling Intermediates" above for details.) - -`ca.crt` might then seem like the more generally correct choice but it's important to bear in mind that it can only ever -be populated on a best-effort basis. The contents of `ca.crt` depend on the `Issuer` being configured correctly, and some -issuer types may not ever be able to provide a useful or correct entry for this field. - -As a rule, you should prefer to create `Bundles` exclusively using root certificates (again, see above), and so you should -only use whichever field has a single root certificate in it. Consider reading below about why you might not want to -actually rely directly on cert-manager-issued certificates. - -### cert-manager Integration: Intentionally Copying CA Certificates - -It's very strange in the Kubernetes world to suggest intentionally adding a step which seems to make automating infrastructure -harder, but in the case of TLS trust stores it can be a wise choice. - -Say you have a cert-manager `Issuer` which has the root certificate you want to trust in `ca.crt`. It's tempting to -use the `Secret` directly and point at `ca.crt`, but a best practice would be to copy that root into a separate `ConfigMap` -(or `Secret`). - -The reason is - as with many TLS gotchas - certificate rotation. If you rotate your issuer such that it's issued from a new root -certificate, trust-manager will see the `Secret` be updated and automatically update your trust bundle to include the new root - -immediately distrusting the old root. - -That means that if any services were still using a certificate issued by the old root, they'll be distrusted and will break. - -Rotation requires that both root certificates are trusted simultaneously for a period, or else that all issued certificates -are rotated either before or at the same time as the old root. - -## Known Issues - -### `kubectl describe` - -The `useDefaultCAs` option hits a corner case inside `kubectl describe` and is rendered as `Use Default C As: true`. This is -purely cosmetic. diff --git a/content/v1.13-docs/projects/trust-manager/api-reference.md b/content/v1.13-docs/projects/trust-manager/api-reference.md deleted file mode 100644 index da09c7c039..0000000000 --- a/content/v1.13-docs/projects/trust-manager/api-reference.md +++ /dev/null @@ -1,592 +0,0 @@ ---- -title: trust-manager API Reference -description: "trust-manager API documentation for custom resources" ---- - -Packages: - -- [`trust.cert-manager.io/v1alpha1`](#trustcert-manageriov1alpha1) - -# `trust.cert-manager.io/v1alpha1` - -Resource Types: - - -- [Bundle](#bundle) - - - - -## `Bundle` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              apiVersionstringtrust.cert-manager.io/v1alpha1true
              kindstringBundletrue
              metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
              specobject - Desired state of the Bundle resource.
              -
              true
              statusobject - Status of the Bundle. This is set and managed automatically.
              -
              false
              - - -### `Bundle.spec` - - -Desired state of the Bundle resource. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              sources[]object - Sources is a set of references to data whose data will sync to the target.
              -
              true
              targetobject - Target is the target location in all namespaces to sync source data to.
              -
              true
              - - -### `Bundle.spec.sources[index]` - - -BundleSource is the set of sources whose data will be appended and synced to the BundleTarget in all Namespaces. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              configMapobject - ConfigMap is a reference to a ConfigMap's `data` key, in the trust Namespace.
              -
              false
              inLinestring - InLine is a simple string to append as the source data.
              -
              false
              secretobject - Secret is a reference to a Secrets's `data` key, in the trust Namespace.
              -
              false
              useDefaultCAsboolean - UseDefaultCAs, when true, requests the default CA bundle to be used as a source. Default CAs are available if trust-manager was installed via Helm or was otherwise set up to include a package-injecting init container by using the "--default-package-location" flag when starting the trust-manager controller. If default CAs were not configured at start-up, any request to use the default CAs will fail. The version of the default CA package which is used for a Bundle is stored in the defaultCAPackageVersion field of the Bundle's status field.
              -
              false
              - - -### `Bundle.spec.sources[index].configMap` - - -ConfigMap is a reference to a ConfigMap's `data` key, in the trust Namespace. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              namestring - Name is the name of the source object in the trust Namespace.
              -
              true
              - - -### `Bundle.spec.sources[index].secret` - - -Secret is a reference to a Secrets's `data` key, in the trust Namespace. - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              namestring - Name is the name of the source object in the trust Namespace.
              -
              true
              - - -### `Bundle.spec.target` - - -Target is the target location in all namespaces to sync source data to. - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              additionalFormatsobject - AdditionalFormats specifies any additional formats to write to the target
              -
              false
              configMapobject - ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
              -
              false
              namespaceSelectorobject - NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
              -
              false
              - - -### `Bundle.spec.target.additionalFormats` - - -AdditionalFormats specifies any additional formats to write to the target - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              jksobject - JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit".
              -
              false
              - - -### `Bundle.spec.target.additionalFormats.jks` - - -JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit". - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              - - -### `Bundle.spec.target.configMap` - - -ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              - - -### `Bundle.spec.target.namespaceSelector` - - -NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              matchLabelsmap[string]string - MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
              -
              false
              - - -### `Bundle.status` - - -Status of the Bundle. This is set and managed automatically. - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              conditions[]object - List of status conditions to indicate the status of the Bundle. Known condition types are `Bundle`.
              -
              false
              defaultCAVersionstring - DefaultCAPackageVersion, if set and non-empty, indicates the version information which was retrieved when the set of default CAs was requested in the bundle source. This should only be set if useDefaultCAs was set to "true" on a source, and will be the same for the same version of a bundle with identical certificates.
              -
              false
              targetobject - Target is the current Target that the Bundle is attempting or has completed syncing the source data to.
              -
              false
              - - -### `Bundle.status.conditions[index]` - - -BundleCondition contains condition information for a Bundle. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              statusstring - Status of the condition, one of ('True', 'False', 'Unknown').
              -
              true
              typestring - Type of the condition, known values are (`Synced`).
              -
              true
              lastTransitionTimestring - LastTransitionTime is the timestamp corresponding to the last status change of this condition.
              -
              - Format: date-time
              -
              false
              messagestring - Message is a human readable description of the details of the last transition, complementing reason.
              -
              false
              observedGenerationinteger - If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Bundle.
              -
              - Format: int64
              -
              false
              reasonstring - Reason is a brief machine readable explanation for the condition's last transition.
              -
              false
              - - -### `Bundle.status.target` - - -Target is the current Target that the Bundle is attempting or has completed syncing the source data to. - - - - - - - - - - - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              additionalFormatsobject - AdditionalFormats specifies any additional formats to write to the target
              -
              false
              configMapobject - ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
              -
              false
              namespaceSelectorobject - NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
              -
              false
              - - -### `Bundle.status.target.additionalFormats` - - -AdditionalFormats specifies any additional formats to write to the target - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              jksobject - JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit".
              -
              false
              - - -### `Bundle.status.target.additionalFormats.jks` - - -JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit". - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              - - -### `Bundle.status.target.configMap` - - -ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              keystring - Key is the key of the entry in the object's `data` field to be used.
              -
              true
              - - -### `Bundle.status.target.namespaceSelector` - - -NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector. - - - - - - - - - - - - - - - - -
              NameTypeDescriptionRequired
              matchLabelsmap[string]string - MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
              -
              false
              From ca305213c97fb09e6f4df142042d606388c0a2cb Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 13 Sep 2023 14:48:53 +0200 Subject: [PATCH 150/264] fix supported-releases page & clarify the LTS release Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/installation/supported-releases.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/content/docs/installation/supported-releases.md b/content/docs/installation/supported-releases.md index 29e5ba4d9a..4626fbb37c 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/installation/supported-releases.md @@ -19,7 +19,12 @@ and other world events. | Release | Release Date | End of Life | [Supported Kubernetes versions][s] | [Supported OpenShift versions][s] | |----------|:------------:|:----------------------:|:----------------------------------:|:---------------------------------:| | [1.13][] | Sep 12, 2023 | Release of 1.15 | 1.23 → 1.28 | 4.10 → 4.15 | -| [1.13.0] | May 19, 2023 | Release of 1.14 | 1.22 → 1.27 | 4.9 → 4.14 | +| [1.12][] | May 19, 2023 | Release of 1.14* | 1.22 → 1.27 | 4.9 → 4.14 | + +\*This is an LTS release sponsored by [Venafi](https://www.venafi.com/). +It will be supported for a longer period of time than other releases. +The EOL date for this release has not yet been determined & communicated, but can be decided by the Sponsor. +The cert-manager maintainers guarantee that this release will be supported until the release of 1.14. ## Upcoming releases @@ -53,8 +58,9 @@ Dates in the future are uncertain and might change. | [0.11][] | Oct 10, 2019 | Jan 21, 2020 | 1.9 → 1.21 | 3.09 → 4.7 | [s]: #kubernetes-supported-versions -[1.13]: https://github.com/cert-manager/cert-manager/milestone/34 -[1.13.0 https://cert-manager.io/docs/release-notes/release-notes-1.12 +[1.14]: https://github.com/cert-manager/cert-manager/milestone/35 +[1.13]: https://cert-manager.io/docs/release-notes/release-notes-1.13 +[1.12]: https://cert-manager.io/docs/release-notes/release-notes-1.12 [1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 [1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 [1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 From 4157e5e77e6c5eaffae2d42e97edbbb314f812f4 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 13 Sep 2023 17:59:37 +0200 Subject: [PATCH 151/264] add upgrade notes to the manifest.json file Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/manifest.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 8e926ced06..c945ef8c6b 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -66,6 +66,10 @@ "title": "Migrating Deprecated API Resources", "path": "/docs/installation/upgrading/remove-deprecated-apis.md" }, + { + "title": "v1.12 to v1.13", + "path": "/docs/installation/upgrading/upgrading-1.12-1.13.md" + }, { "title": "v1.11 to v1.12", "path": "/docs/installation/upgrading/upgrading-1.11-1.12.md" From 3acae8a43ff03abc042eab24737c7effa97ede85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Valais?= Date: Fri, 15 Sep 2023 13:58:03 +0200 Subject: [PATCH 152/264] release-process: remove duplicate explanation about github token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Valais --- content/docs/contributing/release-process.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 05d74eda5a..cedd546f40 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -494,15 +494,11 @@ page if a step is missing or if it is outdated. --output github-release-description.md ``` -

              - The GitHub token **does not need any scope**. The token is required - only to avoid rate-limits imposed on anonymous API users. -

              -

              The GitHub token **does not need any scope**. The token is required only to avoid rate-limits imposed on anonymous API users.

              + 3. Add a one-sentence summary at the top. 4. **(final release only)** Write the "Community" section, following the example of past releases such as [v1.12.0](https://github.com/cert-manager/cert-manager/releases/tag/v1.12.0). If there are any users who didn't make code contributions but helped in other ways (testing, PR discussion, etc), be sure to thank them here! From a05856e652960863f94c1acb085f9da19eb2a9f4 Mon Sep 17 00:00:00 2001 From: Nicolas Pais Date: Sun, 3 Sep 2023 12:14:57 -0300 Subject: [PATCH 153/264] Add clarification to selfsigned bootstrapping Signed-off-by: Nicolas Pais --- content/docs/configuration/selfsigned.md | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/content/docs/configuration/selfsigned.md b/content/docs/configuration/selfsigned.md index bfc2b39b2b..ab949678f5 100644 --- a/content/docs/configuration/selfsigned.md +++ b/content/docs/configuration/selfsigned.md @@ -117,6 +117,48 @@ spec: secretName: root-secret ``` +Alternatively, if you are looking to use `ClusterIssuer` for signing `Certificates` anywhere in your cluster with the `SelfSigned` `Certificate` CA, use the YAML below (slight modification to the last step): + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: sandbox +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: my-selfsigned-ca + namespace: sandbox +spec: + isCA: true + commonName: my-selfsigned-ca + secretName: root-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + group: cert-manager.io +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: my-ca-issuer +spec: + ca: + secretName: root-secret +``` +The first `ClusterIssuer` is used to sign the Root CA, in this case. The second `ClusterIssuer` is used to sign certificates using said Root CA. + ### CRL Distribution Points You may also optionally specify [CRL](https://en.wikipedia.org/wiki/Certificate_revocation_list) From 81bf3ef1bb9b81d0d6ac5684af97cdd7d1da6772 Mon Sep 17 00:00:00 2001 From: Nicolas Pais Date: Thu, 14 Sep 2023 12:11:29 -0300 Subject: [PATCH 154/264] Update namespace delcaration Cluster issuer will look for the secret in the namespace where cert-manager is installed Co-authored-by: Erik Godding Boye Signed-off-by: Nicolas Pais --- content/docs/configuration/selfsigned.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/configuration/selfsigned.md b/content/docs/configuration/selfsigned.md index ab949678f5..5a0de5d1cb 100644 --- a/content/docs/configuration/selfsigned.md +++ b/content/docs/configuration/selfsigned.md @@ -136,7 +136,7 @@ apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: my-selfsigned-ca - namespace: sandbox + namespace: cert-manager spec: isCA: true commonName: my-selfsigned-ca From b97f9c0557673bd3b0502c1a7322f44b832b9525 Mon Sep 17 00:00:00 2001 From: Nicolas Pais Date: Thu, 14 Sep 2023 12:18:30 -0300 Subject: [PATCH 155/264] Refer to ClusterIssuer by name, reword for clarity Signed-off-by: Nicolas Pais --- content/docs/configuration/selfsigned.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/configuration/selfsigned.md b/content/docs/configuration/selfsigned.md index 5a0de5d1cb..22623da1e1 100644 --- a/content/docs/configuration/selfsigned.md +++ b/content/docs/configuration/selfsigned.md @@ -157,7 +157,7 @@ spec: ca: secretName: root-secret ``` -The first `ClusterIssuer` is used to sign the Root CA, in this case. The second `ClusterIssuer` is used to sign certificates using said Root CA. +The "selfsigned-issuer" `ClusterIssuer` is used to issue the Root CA Certificate. Then, "my-ca-issuer" `ClusterIssuer` is used to issue but also sign certificates using the newly created Root CA `Certificate`, which is what you will use for future certificates cluster-wide. ### CRL Distribution Points From f28e9aa8e26225117551e62fbef762e8bf26fed1 Mon Sep 17 00:00:00 2001 From: Nicolas Pais Date: Mon, 18 Sep 2023 19:34:22 -0300 Subject: [PATCH 156/264] Add selfsigned-issuer to .spelling Signed-off-by: Nicolas Pais --- .spelling | 1 + 1 file changed, 1 insertion(+) diff --git a/.spelling b/.spelling index a32d71275b..1612112ea7 100644 --- a/.spelling +++ b/.spelling @@ -648,6 +648,7 @@ zhangzhiqiangcs arukiidou Richardds kahirokunn +selfsigned-issuer # TEMPORARY # these are temporarily ignored because the spellchecker From acd07ff5b78f10b258e9be70443cf0292b2d1852 Mon Sep 17 00:00:00 2001 From: Hayden <49427552+m8rmclaren@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:15:25 -0700 Subject: [PATCH 157/264] Add ejbca-issuer to external issuers list Signed-off-by: Hayden <49427552+m8rmclaren@users.noreply.github.com> --- content/docs/configuration/external.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/docs/configuration/external.md b/content/docs/configuration/external.md index d4bc78088f..cf34b430bf 100644 --- a/content/docs/configuration/external.md +++ b/content/docs/configuration/external.md @@ -45,6 +45,7 @@ These external issuers are known to support and honor [approval](https://cert-ma - [CFSSL Issuer](https://gerrit.wikimedia.org/r/plugins/gitiles/operations/software/cfssl-issuer/): Request certificates signed by a [CFSSL](https://github.com/cloudflare/cfssl) `multirootca` instance. - [ncm-issuer](https://github.com/nokia/ncm-issuer): Requests certificates from the [Nokia](https://www.nokia.com/) [Netguard Certificate Manager](https://www.nokia.com/networks/security-portfolio/netguard/certificate-manager) - [tcs-issuer](https://github.com/intel/trusted-certificate-issuer) Requests certificates signed securely using [Intel's SGX technology](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/overview.html). +- [ejbca-issuer](https://github.com/Keyfactor/ejbca-cert-manager-issuer): Request certificates from [EJBCA](https://www.ejbca.org/). ## Building New External Issuers From 50df15579c588a55ad3975c64f8b57aad2ec40b3 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Wed, 20 Sep 2023 13:38:12 +0100 Subject: [PATCH 158/264] update csi-driver project page This page has a lot of prose which I think benefits from clarification. There are also many typos and the first section of our installation instructions talks about an ancient Kubernetes version, with instructions which will apply to approximately nobody. This PR should hopefully make the docs simpler and easier to follow. Signed-off-by: Ashley Davis --- content/docs/projects/csi-driver.md | 130 ++++++++++++++-------------- 1 file changed, 63 insertions(+), 67 deletions(-) diff --git a/content/docs/projects/csi-driver.md b/content/docs/projects/csi-driver.md index 31f7f1c74d..5ede7c4815 100644 --- a/content/docs/projects/csi-driver.md +++ b/content/docs/projects/csi-driver.md @@ -1,42 +1,30 @@ --- title: csi-driver -description: '' +description: 'Mounting cert-manager certificates without secrets' --- -csi-driver is a Container Storage Interface (CSI) driver plugin for Kubernetes -to work along cert-manager. The goal for this plugin is to seamlessly request -and mount certificate key pairs to pods. This is useful for facilitating mTLS, -or otherwise securing connections of pods with guaranteed present certificates -whilst having all of the features that cert-manager provides. - -## Why a CSI Driver? - -- Ensure private keys never leave the node and are never sent over the network. - All private keys are stored locally on the node. -- Unique key and certificate per application replica with a grantee to be - present on application run time. -- Reduce resource management overhead by defining certificate request spec - in-line of the Kubernetes Pod template. -- Automatic renewal of certificates based on expiry of each individual - certificate. -- Keys and certificates are destroyed during application termination. -- Scope for extending plugin behavior with visibility on each replica's - certificate request and termination. - -## Requirements and Installation - -This CSI driver plugin makes use of the 'CSI inline volume' feature - Alpha as -of `v1.15` and beta in `v1.16`. Kubernetes versions `v1.16` and higher require -no extra configuration however `v1.15` requires the following feature gate set: -``` ---feature-gates=CSIInlineVolume=true -``` +csi-driver is a [Container Storage Interface (CSI)](https://kubernetes-csi.github.io/docs/) driver plugin for Kubernetes +which works alongside cert-manager. + +Pods which mount the cert-manager csi-driver will request certificates from cert-manager +without needing a `Certificate` resource to be created. These certificates will be mounted +directly into the pod, with no intermediate Secret being created. + +## Why use csi-driver? + +- Ensure private keys never leave the node and are never sent over the network. All private keys are stored locally on the node +- Unique key and certificate per application replica. +- Fewer `Certificate` resources means writing less YAML +- Keys and certificates are destroyed when an application terminates +- No `Secret` resources needed for storing the certificate means less RBAC + +## Installation You must have a working installation of cert-manager present on the cluster. -Instructions on how to install cert-manager can be found -[on cert-manager.io](../installation/README.md). -To install the csi-driver, use helm install: +Instructions on how to install cert-manager can be found [on this website](../installation/README.md). + +To install csi-driver, use helm: ```terminal helm repo add jetstack https://charts.jetstack.io --force-update @@ -55,7 +43,7 @@ You can verify the installation has completed correctly by checking the presence of the CSIDriver resource as well as a CSINode resource present for each node, referencing `csi.cert-manager.io`. -``` +```terminal $ kubectl get csidrivers NAME CREATED AT csi.cert-manager.io 2019-09-06T16:55:19Z @@ -80,17 +68,29 @@ items: ... ``` -The CSI driver is now installed and is ready to be used for pods in the cluster. +### Kubernetes Requirements -## Requesting and Mounting Certificates +This CSI driver plugin makes use of the 'CSI inline volume' feature which was Alpha as +of Kubernetes `v1.15` and Beta in `v1.16`. + +This means that Kubernetes versions `v1.16` and higher require no extra configuration to be +able to run csi-driver. -To request certificates from cert-manager, simply define a volume mount where -the key and certificate will be written to, along with a volume with attributes -that define the cert-manager request. The following is a dummy app that mounts a -key certificate pair to `/tls` and has been signed by the `ca-issuer` with a DNS -name valid for `my-service.sandbox.svc.cluster.local`. +Using Kubernetes `v1.15` requires the following feature gate to be set: +```text +--feature-gates=CSIInlineVolume=true ``` + +## Requesting and Mounting Certificates + +Requesting a certificate using csi-driver means mounting a volume, with some attributes +set to define exactly what you need to request. + +The following example is a dummy app that mounts a key certificate pair to `/tls`, signed using +a cert-manager issuer called `ca-issuer` with a DNS name valid for `my-service.sandbox.svc.cluster.local`. + +```yaml apiVersion: v1 kind: Pod metadata: @@ -115,25 +115,21 @@ spec: csi.cert-manager.io/dns-names: ${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local ``` -Once created, the CSI driver will generate a private key locally, request a -certificate from cert-manager based on the given attributes, then store both -locally to be mounted to the pod. The pod will remain in a pending state until -this process has been completed. +Once created, the CSI driver will generate a private key locally (for the pod), request a +certificate from cert-manager based on the given attributes, and store the certificate ready for the pod to use. + +The pod will remain in a pending state until issuance has been completed. + +For more information on how to set up issuers for your cluster, refer to the cert-manager documentation +[here](../configuration/README.md). -For more information on how to set up issuers for your cluster, refer to the -cert-manager documentation -[here](../configuration/README.md). **Note** it is not -possible to use `SelfSigned` Issuers with the CSI Driver. In order for -cert-manager to self sign a certificate, it needs access to the secret -containing the private key that signed the certificate request to sign the end -certificate. This secret is not used and so not available in the CSI driver use -case. +**Note** it is not possible to use `SelfSigned` Issuers with csi-driver because `SelfSigned` issuers are a +special case. ## Supported Volume Attributes The csi-driver driver aims to have complete feature parity with all possible -values available through the cert-manager API however currently supports the -following values; +values available through the cert-manager API. It currently supports the following values: | Attribute | Description | Default | Example | |-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|----------------------------------| @@ -164,17 +160,17 @@ The following attributes support variables that are evaluated when a request is made for the mounting Pod. These variables are useful for constructing requests with SANs that contain values from the mounting Pod. -``` -`csi.cert-manager.io/common-name` -`csi.cert-manager.io/dns-names` -`csi.cert-manager.io/uri-sans` +```text +csi.cert-manager.io/common-name +csi.cert-manager.io/dns-names +csi.cert-manager.io/uri-sans ``` Variables follow the [go `os.Expand`](https://pkg.go.dev/os#Expand) structure, which is generally what you would expect on a UNIX shell. The CSI driver has access to the following variables: -``` +```text ${POD_NAME} ${POD_NAMESPACE} ${POD_UID} @@ -195,18 +191,18 @@ volumeAttributes: If the flag `--use-token-request` is enabled on the csi-driver DaemonSet, the [CertificateRequest](../concepts/certificaterequest.md) resource will be created -by the mounting Pod's ServiceAccount. This can be pared with -[approver-policy](./approver-policy/README.md) to enable advanced policy on a per -ServiceAccount basis. +by the mounting Pod's ServiceAccount. This can be paired with +[approver-policy](./approver-policy/README.md) to enable advanced policy control +on a per-ServiceAccount basis. -Ensure to give permissions to Pod ServiceAccounts to create CertificateRequests +Ensure that you give permissions to Pod ServiceAccounts to create CertificateRequests with this flag enabled, i.e: ```yaml -# WARNING: This RBAC will enable any identiy in the cluster to create -# CertificateRequests. This may or may not be problimatic based on your security -# model. It is likely worth scoping the set of identities in the -# `ClusterRoleBinding` `subjects` stanza. +# WARNING: This RBAC will enable any identity in the cluster to create +# CertificateRequests and is dangerous to use in production. Instead, you should +# give permissions only to identities which need to be able to create certificates. +# This would be done via scoping the set of identities in the `ClusterRoleBinding` `subjects` stanza. kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: From 56811a3bf5b25027a9a219fae249927b95d1b81c Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Wed, 20 Sep 2023 17:28:54 +0100 Subject: [PATCH 159/264] addressing review comments Signed-off-by: Ashley Davis --- content/docs/projects/csi-driver.md | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/content/docs/projects/csi-driver.md b/content/docs/projects/csi-driver.md index 5ede7c4815..81823f0ae0 100644 --- a/content/docs/projects/csi-driver.md +++ b/content/docs/projects/csi-driver.md @@ -12,15 +12,15 @@ directly into the pod, with no intermediate Secret being created. ## Why use csi-driver? -- Ensure private keys never leave the node and are never sent over the network. All private keys are stored locally on the node -- Unique key and certificate per application replica. +- Ensure private keys never leave the node and are never sent over the network. Private keys are stored in memory, and shouldn't be written to disk. +- Unique key and certificate per application replica - Fewer `Certificate` resources means writing less YAML - Keys and certificates are destroyed when an application terminates - No `Secret` resources needed for storing the certificate means less RBAC ## Installation -You must have a working installation of cert-manager present on the cluster. +You must have a working installation of cert-manager present on your cluster and be running at least Kubernetes v1.16. Instructions on how to install cert-manager can be found [on this website](../installation/README.md). @@ -68,20 +68,6 @@ items: ... ``` -### Kubernetes Requirements - -This CSI driver plugin makes use of the 'CSI inline volume' feature which was Alpha as -of Kubernetes `v1.15` and Beta in `v1.16`. - -This means that Kubernetes versions `v1.16` and higher require no extra configuration to be -able to run csi-driver. - -Using Kubernetes `v1.15` requires the following feature gate to be set: - -```text ---feature-gates=CSIInlineVolume=true -``` - ## Requesting and Mounting Certificates Requesting a certificate using csi-driver means mounting a volume, with some attributes From 421f5f508fbff856d4ad44c47af24a90ed9226db Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Wed, 20 Sep 2023 18:01:55 +0100 Subject: [PATCH 160/264] review comments part 2 Signed-off-by: Ashley Davis --- content/docs/projects/csi-driver.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/content/docs/projects/csi-driver.md b/content/docs/projects/csi-driver.md index 81823f0ae0..9bd10b2dad 100644 --- a/content/docs/projects/csi-driver.md +++ b/content/docs/projects/csi-driver.md @@ -17,10 +17,16 @@ directly into the pod, with no intermediate Secret being created. - Fewer `Certificate` resources means writing less YAML - Keys and certificates are destroyed when an application terminates - No `Secret` resources needed for storing the certificate means less RBAC +- Great for ephemeral, short-lived certificates which don't need to survive a restart (e.g. certificates for mTLS) + +## Why _not_ use csi-driver? + +- If you need certificates to be persisted through a node restart +- If you need the same certificate to be shared by multiple components ## Installation -You must have a working installation of cert-manager present on your cluster and be running at least Kubernetes v1.16. +You must have a working installation of cert-manager present on your cluster and be running at least Kubernetes `v1.16`. Instructions on how to install cert-manager can be found [on this website](../installation/README.md). From 2b7cab32dbb0925509d56701dadfa6d9936356a3 Mon Sep 17 00:00:00 2001 From: Hayden <49427552+m8rmclaren@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:39:54 -0700 Subject: [PATCH 161/264] Add `ejbca` as spelling exception to .spelling Signed-off-by: Hayden <49427552+m8rmclaren@users.noreply.github.com> --- .spelling | 1 + 1 file changed, 1 insertion(+) diff --git a/.spelling b/.spelling index 1612112ea7..381178d532 100644 --- a/.spelling +++ b/.spelling @@ -139,6 +139,7 @@ etcd EC2 enj ECDSA +ejbca EKS ELB Ed25519 From a70fc5873c4590ff8d52f761b3e43772c71febe2 Mon Sep 17 00:00:00 2001 From: Hayden <49427552+m8rmclaren@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:27:56 -0700 Subject: [PATCH 162/264] Add command-issuer to external issuers list Signed-off-by: Hayden <49427552+m8rmclaren@users.noreply.github.com> --- content/docs/configuration/external.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/docs/configuration/external.md b/content/docs/configuration/external.md index cf34b430bf..4cad250c44 100644 --- a/content/docs/configuration/external.md +++ b/content/docs/configuration/external.md @@ -46,6 +46,7 @@ These external issuers are known to support and honor [approval](https://cert-ma - [ncm-issuer](https://github.com/nokia/ncm-issuer): Requests certificates from the [Nokia](https://www.nokia.com/) [Netguard Certificate Manager](https://www.nokia.com/networks/security-portfolio/netguard/certificate-manager) - [tcs-issuer](https://github.com/intel/trusted-certificate-issuer) Requests certificates signed securely using [Intel's SGX technology](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/overview.html). - [ejbca-issuer](https://github.com/Keyfactor/ejbca-cert-manager-issuer): Request certificates from [EJBCA](https://www.ejbca.org/). +- [command-issuer](https://github.com/Keyfactor/command-cert-manager-issuer): Request certificates from [Keyfactor Command](https://www.keyfactor.com/products/command/). ## Building New External Issuers From 71806e9543bca4e3a6f9d9a6a847b1c35f037150 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:52:53 +0200 Subject: [PATCH 163/264] update feature flags page to reflect changes made in 1.13 Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 1 + content/docs/installation/featureflags.md | 10 +++++++--- content/v1.13-docs/installation/featureflags.md | 10 +++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.spelling b/.spelling index 381178d532..218047dd06 100644 --- a/.spelling +++ b/.spelling @@ -617,6 +617,7 @@ v1.27.1 v0.6.0. v4.4.1 v1.13.0 +v1.13. liveness apiservices arm64 diff --git a/content/docs/installation/featureflags.md b/content/docs/installation/featureflags.md index 65ce69a027..e84ea50781 100644 --- a/content/docs/installation/featureflags.md +++ b/content/docs/installation/featureflags.md @@ -60,8 +60,6 @@ See `--feature-gates` flags on cert-manager controller and webhook to enable any - `ServerSideApply`. Added in cert-manager 1.8.0. If this feature is enabled, cert-manager uses [Server side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) when creating or updating API resources. This will speed cert-manager operations and prevent the resource version conflict errors. See [release notes](../release-notes/release-notes-1.8.md#server-side-apply) -- `StableCertificateRequestName`. Added in cert-manager 1.10.0. Will enable generation of `CertificateRequest` resources with a fixed name. See [`cert-manager#5487`](https://github.com/cert-manager/cert-manager/pull/5487) - - `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.12.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) - `ValidateCAA`. Added in cert-manager 0.7.2. CAA checking when issuing a certificate. @@ -69,4 +67,10 @@ See `--feature-gates` flags on cert-manager controller and webhook to enable any ### Beta -There are currently no beta feature gates +These features are enabled by default. See `--feature-gates` flags on cert-manager controller and webhook to disable any of these features. + +- `StableCertificateRequestName`. Alpha in v1.10 and Beta in v1.13. Enables generation of `CertificateRequest` resources with a fixed name. See [`cert-manager#5487`](https://github.com/cert-manager/cert-manager/pull/5487) + +- `SecretsFilteredCaching`. Alpha in v1.12 and Beta in v1.13. Reduces controller's memory consumption by filtering which Secrets are cached in full using `controller.cert-manager.io/fao` label. By default all Certificate Secrets are labelled with `controller.cert-manager.io/fao` label. Users can also label other Secrets, such as issuer credentials Secrets that they know cert-manager will need access to to speed up issuance. See [`20221205-memory-management.md`](https://github.com/cert-manager/cert-manager/blob/master/design/20221205-memory-management.md) + +- `DisallowInsecureCSRUsageDefinition`. Beta in v1.13. Prevents the webhook from allowing CertificateRequest's usages to be only defined in the CSR, while leaving the usages field empty. diff --git a/content/v1.13-docs/installation/featureflags.md b/content/v1.13-docs/installation/featureflags.md index 65ce69a027..e84ea50781 100644 --- a/content/v1.13-docs/installation/featureflags.md +++ b/content/v1.13-docs/installation/featureflags.md @@ -60,8 +60,6 @@ See `--feature-gates` flags on cert-manager controller and webhook to enable any - `ServerSideApply`. Added in cert-manager 1.8.0. If this feature is enabled, cert-manager uses [Server side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) when creating or updating API resources. This will speed cert-manager operations and prevent the resource version conflict errors. See [release notes](../release-notes/release-notes-1.8.md#server-side-apply) -- `StableCertificateRequestName`. Added in cert-manager 1.10.0. Will enable generation of `CertificateRequest` resources with a fixed name. See [`cert-manager#5487`](https://github.com/cert-manager/cert-manager/pull/5487) - - `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.12.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) - `ValidateCAA`. Added in cert-manager 0.7.2. CAA checking when issuing a certificate. @@ -69,4 +67,10 @@ See `--feature-gates` flags on cert-manager controller and webhook to enable any ### Beta -There are currently no beta feature gates +These features are enabled by default. See `--feature-gates` flags on cert-manager controller and webhook to disable any of these features. + +- `StableCertificateRequestName`. Alpha in v1.10 and Beta in v1.13. Enables generation of `CertificateRequest` resources with a fixed name. See [`cert-manager#5487`](https://github.com/cert-manager/cert-manager/pull/5487) + +- `SecretsFilteredCaching`. Alpha in v1.12 and Beta in v1.13. Reduces controller's memory consumption by filtering which Secrets are cached in full using `controller.cert-manager.io/fao` label. By default all Certificate Secrets are labelled with `controller.cert-manager.io/fao` label. Users can also label other Secrets, such as issuer credentials Secrets that they know cert-manager will need access to to speed up issuance. See [`20221205-memory-management.md`](https://github.com/cert-manager/cert-manager/blob/master/design/20221205-memory-management.md) + +- `DisallowInsecureCSRUsageDefinition`. Beta in v1.13. Prevents the webhook from allowing CertificateRequest's usages to be only defined in the CSR, while leaving the usages field empty. From 3227f45ba3ba410c728d3c3e187084865221df6e Mon Sep 17 00:00:00 2001 From: Hayden <49427552+m8rmclaren@users.noreply.github.com> Date: Thu, 21 Sep 2023 13:53:25 -0700 Subject: [PATCH 164/264] Add Keyfactor to list of known words Signed-off-by: Hayden <49427552+m8rmclaren@users.noreply.github.com> --- .spelling | 1 + 1 file changed, 1 insertion(+) diff --git a/.spelling b/.spelling index 381178d532..64a3cb2384 100644 --- a/.spelling +++ b/.spelling @@ -183,6 +183,7 @@ JoshVanL Jsonnet Juneezee JoooostB +Keyfactor KeySelector KUARD Kirill-Garbar From ed8a5baefb758fca61519bcec3df6ce71ef7199b Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:37:38 +0200 Subject: [PATCH 165/264] update latest version from v1.13.0 to v1.13.1 Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/installation/README.md | 2 +- content/docs/installation/code-signing.md | 2 +- content/docs/installation/helm.md | 10 +++++----- content/docs/installation/kubectl.md | 2 +- .../docs/installation/operator-lifecycle-manager.md | 2 +- content/v1.13-docs/installation/README.md | 2 +- content/v1.13-docs/installation/code-signing.md | 2 +- content/v1.13-docs/installation/helm.md | 10 +++++----- content/v1.13-docs/installation/kubectl.md | 2 +- .../installation/operator-lifecycle-manager.md | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/content/docs/installation/README.md b/content/docs/installation/README.md index 39fcd350f3..1dd0eaec36 100644 --- a/content/docs/installation/README.md +++ b/content/docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/docs/installation/code-signing.md b/content/docs/installation/code-signing.md index fef64be007..52aa725c6b 100644 --- a/content/docs/installation/code-signing.md +++ b/content/docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.13.0 # change as needed +IMAGE_TAG=v1.13.1 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index 055a1ce82b..c979d63424 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -47,7 +47,7 @@ section below for details on each method. > Recommended for production installations ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -70,7 +70,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.0 \ + --version v1.13.1 \ # --set installCRDs=true ``` @@ -83,7 +83,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.0 \ + --version v1.13.1 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -114,7 +114,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.13.0 + version: v1.13.1 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -148,7 +148,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.0 \ + --version v1.13.1 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index 225b229662..117ccb884b 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -19,7 +19,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index eb431286ff..b525c41edb 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.13.0 \ +kubectl patch csv cert-manager.v1.13.1 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` diff --git a/content/v1.13-docs/installation/README.md b/content/v1.13-docs/installation/README.md index 39fcd350f3..1dd0eaec36 100644 --- a/content/v1.13-docs/installation/README.md +++ b/content/v1.13-docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/v1.13-docs/installation/code-signing.md b/content/v1.13-docs/installation/code-signing.md index fef64be007..52aa725c6b 100644 --- a/content/v1.13-docs/installation/code-signing.md +++ b/content/v1.13-docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.13.0 # change as needed +IMAGE_TAG=v1.13.1 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/v1.13-docs/installation/helm.md b/content/v1.13-docs/installation/helm.md index 055a1ce82b..c979d63424 100644 --- a/content/v1.13-docs/installation/helm.md +++ b/content/v1.13-docs/installation/helm.md @@ -47,7 +47,7 @@ section below for details on each method. > Recommended for production installations ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -70,7 +70,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.0 \ + --version v1.13.1 \ # --set installCRDs=true ``` @@ -83,7 +83,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.0 \ + --version v1.13.1 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -114,7 +114,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.13.0 + version: v1.13.1 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -148,7 +148,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.0 \ + --version v1.13.1 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/v1.13-docs/installation/kubectl.md b/content/v1.13-docs/installation/kubectl.md index 225b229662..117ccb884b 100644 --- a/content/v1.13-docs/installation/kubectl.md +++ b/content/v1.13-docs/installation/kubectl.md @@ -19,7 +19,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/v1.13-docs/installation/operator-lifecycle-manager.md b/content/v1.13-docs/installation/operator-lifecycle-manager.md index eb431286ff..b525c41edb 100644 --- a/content/v1.13-docs/installation/operator-lifecycle-manager.md +++ b/content/v1.13-docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.13.0 \ +kubectl patch csv cert-manager.v1.13.1 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` From cb0e98a74055bba666a85e29854b89c6fa1a31a6 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:42:02 +0200 Subject: [PATCH 166/264] add 1.12.5 release notes Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/release-notes/release-notes-1.12.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/content/docs/release-notes/release-notes-1.12.md b/content/docs/release-notes/release-notes-1.12.md index 3f964d4bf3..ec906dd03d 100644 --- a/content/docs/release-notes/release-notes-1.12.md +++ b/content/docs/release-notes/release-notes-1.12.md @@ -3,6 +3,21 @@ title: Release 1.12 description: 'cert-manager release notes: cert-manager 1.12' --- +## v1.12.5 + +v1.12.5 contains a backport for a name collision bug that was found in v1.13.0 + +### Changes + +#### Bug or Regression + +- BUGFIX: fix CertificateRequest name collision bug in StableCertificateRequestName feature. (#6359, @jetstack-bot) + +#### Other (Cleanup or Flake) + +- Updated base images to the latest version. (#6372, @inteon) +- Upgrade Go from 1.20.7 to 1.20.8. (#6371, @jetstack-bot) + ## v1.12.4 v1.12.4 contains an important security fix that From 83a85e72137e06e1d89c86fc29564d315d2d9640 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:43:48 +0200 Subject: [PATCH 167/264] add 1.13.1 release notes Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/release-notes/release-notes-1.13.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/content/docs/release-notes/release-notes-1.13.md b/content/docs/release-notes/release-notes-1.13.md index 6f52d65a0d..a8595b18fd 100644 --- a/content/docs/release-notes/release-notes-1.13.md +++ b/content/docs/release-notes/release-notes-1.13.md @@ -3,6 +3,21 @@ title: Release 1.13 description: 'cert-manager release notes: cert-manager 1.13' --- +## v1.13.1 + +v1.13.1 contains a bugfix for a name collision bug in the StableCertificateRequestName feature that was enabled by default in v1.13.0. + +### Changes + +#### Bug or Regression + +- BUGFIX: fix CertificateRequest name collision bug in StableCertificateRequestName feature. (#6358, @jetstack-bot) + +#### Other (Cleanup or Flake) + +- Upgrade `github.com/emicklei/go-restful/v3` to `v3.11.0` because `v3.10.2` is labeled as "DO NOT USE". (#6368, @inteon) +- Upgrade Go from 1.20.7 to 1.20.8. (#6370, @jetstack-bot) + ## v1.13.0 cert-manager 1.13 brings support for DNS over HTTPS, support for loading options from a versioned From ee73b4004fb108bce000007eb45e6b3ef8d33e0b Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:45:40 +0200 Subject: [PATCH 168/264] add version to spelling execeptions Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 1 + 1 file changed, 1 insertion(+) diff --git a/.spelling b/.spelling index 218047dd06..5e771e8b67 100644 --- a/.spelling +++ b/.spelling @@ -618,6 +618,7 @@ v0.6.0. v4.4.1 v1.13.0 v1.13. +v1.12.5 liveness apiservices arm64 From f95875146a88508addeb16d9588eb80bb6a5c0a1 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:46:20 +0200 Subject: [PATCH 169/264] add version to spelling execeptions Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.spelling b/.spelling index 218047dd06..c8c6a06b30 100644 --- a/.spelling +++ b/.spelling @@ -617,6 +617,8 @@ v1.27.1 v0.6.0. v4.4.1 v1.13.0 +v1.13.0. +v1.13.1 v1.13. liveness apiservices From 836412798cfac0003fc210d2c230d9ec7f0786b7 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:41:51 +0200 Subject: [PATCH 170/264] update 1.12 installation docs to refer to latest patch release Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/v1.12-docs/installation/README.md | 2 +- content/v1.12-docs/installation/code-signing.md | 2 +- content/v1.12-docs/installation/helm.md | 10 +++++----- content/v1.12-docs/installation/kubectl.md | 2 +- .../installation/operator-lifecycle-manager.md | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/content/v1.12-docs/installation/README.md b/content/v1.12-docs/installation/README.md index e3d09c6c01..741d8e3a98 100644 --- a/content/v1.12-docs/installation/README.md +++ b/content/v1.12-docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.5/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/v1.12-docs/installation/code-signing.md b/content/v1.12-docs/installation/code-signing.md index 4f85faf92e..534a8c97fd 100644 --- a/content/v1.12-docs/installation/code-signing.md +++ b/content/v1.12-docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.12.0 # change as needed +IMAGE_TAG=v1.12.5 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/v1.12-docs/installation/helm.md b/content/v1.12-docs/installation/helm.md index 18a7af9695..4086c63cad 100644 --- a/content/v1.12-docs/installation/helm.md +++ b/content/v1.12-docs/installation/helm.md @@ -44,7 +44,7 @@ or using the `installCRDs` option when installing the Helm chart. ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.5/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -65,7 +65,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.0 \ + --version v1.12.5 \ # --set installCRDs=true ``` @@ -78,7 +78,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.0 \ + --version v1.12.5 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -109,7 +109,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.12.0 + version: v1.12.5 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -140,7 +140,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.0 \ + --version v1.12.5 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/v1.12-docs/installation/kubectl.md b/content/v1.12-docs/installation/kubectl.md index 655f0ecbdf..05a7237a67 100644 --- a/content/v1.12-docs/installation/kubectl.md +++ b/content/v1.12-docs/installation/kubectl.md @@ -19,7 +19,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.5/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/v1.12-docs/installation/operator-lifecycle-manager.md b/content/v1.12-docs/installation/operator-lifecycle-manager.md index 52fa7ad06b..df6a7b6ece 100644 --- a/content/v1.12-docs/installation/operator-lifecycle-manager.md +++ b/content/v1.12-docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.12.0 \ +kubectl patch csv cert-manager.v1.12.5 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` From 104cca9f36f874e10ec4199f313d22efac356a47 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 28 Sep 2023 19:45:34 +0200 Subject: [PATCH 171/264] Restructure top-level menu Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/configuration/ca.md | 2 +- content/docs/configuration/selfsigned.md | 2 +- .../README.md => contributing/projects.md} | 11 +- .../docs/{tutorials => devops-tips}/backup.md | 0 .../prometheus-metrics.md | 0 .../syncing-secrets-across-namespaces.md | 0 content/docs/installation/uninstall.md | 2 +- content/docs/installation/upgrading/README.md | 2 +- .../upgrading/upgrading-0.10-0.11.md | 2 +- .../upgrading/upgrading-0.16-1.0.md | 2 +- .../upgrading/upgrading-0.5-0.6.md | 4 +- .../upgrading/upgrading-0.8-0.9.md | 2 +- .../upgrading/upgrading-1.1-1.2.md | 2 +- content/docs/manifest.json | 232 ++++++++++-------- content/docs/policy/README.md | 0 .../approval/README.md} | 6 +- .../approval}/approver-policy/README.md | 12 +- .../approver-policy/api-reference.md | 0 content/docs/policy/issuing.md | 0 content/docs/trust/README.md | 0 .../trust-manager/README.md | 2 +- .../trust-manager/api-reference.md | 0 content/docs/tutorials/README.md | 4 - .../README.md | 4 +- content/docs/tutorials/istio-csr/istio-csr.md | 2 +- content/docs/usage/README.md | 5 +- .../{projects => usage}/csi-driver-spiffe.md | 0 .../docs/{projects => usage}/csi-driver.md | 2 +- content/docs/usage/csi.md | 2 +- content/docs/{projects => usage}/istio-csr.md | 9 +- content/docs/usage/istio.md | 17 -- package.json | 2 +- public/_redirects | 17 +- scripts/gendocs/generate-trust-manager | 2 +- 34 files changed, 191 insertions(+), 158 deletions(-) rename content/docs/{projects/README.md => contributing/projects.md} (68%) rename content/docs/{tutorials => devops-tips}/backup.md (100%) rename content/docs/{usage => devops-tips}/prometheus-metrics.md (100%) rename content/docs/{tutorials => devops-tips}/syncing-secrets-across-namespaces.md (100%) create mode 100644 content/docs/policy/README.md rename content/docs/{usage/approver-policy.md => policy/approval/README.md} (65%) rename content/docs/{projects => policy/approval}/approver-policy/README.md (97%) rename content/docs/{projects => policy/approval}/approver-policy/api-reference.md (100%) create mode 100644 content/docs/policy/issuing.md create mode 100644 content/docs/trust/README.md rename content/docs/{projects => trust}/trust-manager/README.md (99%) rename content/docs/{projects => trust}/trust-manager/api-reference.md (100%) rename content/docs/{projects => usage}/csi-driver-spiffe.md (100%) rename content/docs/{projects => usage}/csi-driver.md (99%) rename content/docs/{projects => usage}/istio-csr.md (92%) delete mode 100644 content/docs/usage/istio.md diff --git a/content/docs/configuration/ca.md b/content/docs/configuration/ca.md index fffb6f2401..c2a21a34f4 100644 --- a/content/docs/configuration/ca.md +++ b/content/docs/configuration/ca.md @@ -15,7 +15,7 @@ private key are stored inside the cluster as a Kubernetes `Secret`. Certificates issued by a CA issuer will not be publicly trusted and so are unlikely to be trusted by your applications without further configuration. -Consider [trust-manager](../projects/trust-manager/README.md) for distributing your CA certificate safely +Consider [trust-manager](../trust/trust-manager/README.md) for distributing your CA certificate safely across your cluster! ## Deployment diff --git a/content/docs/configuration/selfsigned.md b/content/docs/configuration/selfsigned.md index 22623da1e1..76c92a530e 100644 --- a/content/docs/configuration/selfsigned.md +++ b/content/docs/configuration/selfsigned.md @@ -181,7 +181,7 @@ Clients consuming `SelfSigned` certificates have _no way_ to trust them without already having the certificates beforehand, which can be hard to manage when the client is in a different namespace to the server. -This limitation can be tackled by using [trust-manager](../projects/trust-manager/README.md) to distribute `ca.crt` +This limitation can be tackled by using [trust-manager](../trust/trust-manager/README.md) to distribute `ca.crt` to other namespaces. There is no secure alternative to solving the problem of distributing trust stores; it's possible diff --git a/content/docs/projects/README.md b/content/docs/contributing/projects.md similarity index 68% rename from content/docs/projects/README.md rename to content/docs/contributing/projects.md index 185df2fce4..cd396b08e7 100644 --- a/content/docs/projects/README.md +++ b/content/docs/contributing/projects.md @@ -8,24 +8,23 @@ that extend the project's functionality, and complement the core cert-manager fe These tools help with security, compliance and control. -- [istio-csr](./istio-csr.md): Secure Istio service mesh with istio-csr which is +- [istio-csr](https://github.com/cert-manager/istio-csr) ([docs](../usage/istio-csr.md)): Secure Istio service mesh with istio-csr which is an agent that allows for [Istio](https://istio.io) workload and control plane components to be secured using cert-manager. -- [approver-policy](./approver-policy/README.md): +- [approver-policy](https://github.com/cert-manager/approver-policy) ([docs](../policy/approval/approver-policy/README.md)): a cert-manager **approver** that will automatically approve or deny certificate requests based on defined policy. -- [csi-driver](./csi-driver.md): +- [csi-driver](https://github.com/cert-manager/csi-driver) ([docs](../usage/csi-driver.md)): a Container Storage Interface (CSI) driver plugin for Kubernetes to work along cert-manager. The goal for this plugin is to seamlessly request and mount certificate key pairs to pods. This is useful for facilitating mTLS, or otherwise securing connections of pods with guaranteed present certificates whilst having all of the features that cert-manager provides. -- [csi-driver-spiffe](./csi-driver-spiffe.md): +- [csi-driver-spiffe](https://github.com/cert-manager/csi-driver-spiffe) ([docs](../usage/csi-driver-spiffe.md)): another CSI driver plugin to work along cert-manager. This CSI driver transparently delivers [SPIFFE](https://spiffe.io/) [SVIDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-verifiable-identity-document-svid) in the form of X.509 certificate key pairs to mounting Kubernetes Pods. The end result is all and any Pod running in Kubernetes can securely request their SPIFFE identity document from a Trust Domain with minimal configuration. -- [trust-manager](./trust-manager/README.md): the easiest way to manage TLS trust bundles in Kubernetes and OpenShift clusters. -- [trust-manager API reference](./trust-manager/api-reference.md): full documentation of the trust-manager CRD(s) +- [trust-manager](https://github.com/cert-manager/trust-manager) ([docs](../trust/trust-manager/README.md)): the easiest way to manage TLS trust bundles in Kubernetes and OpenShift clusters. diff --git a/content/docs/tutorials/backup.md b/content/docs/devops-tips/backup.md similarity index 100% rename from content/docs/tutorials/backup.md rename to content/docs/devops-tips/backup.md diff --git a/content/docs/usage/prometheus-metrics.md b/content/docs/devops-tips/prometheus-metrics.md similarity index 100% rename from content/docs/usage/prometheus-metrics.md rename to content/docs/devops-tips/prometheus-metrics.md diff --git a/content/docs/tutorials/syncing-secrets-across-namespaces.md b/content/docs/devops-tips/syncing-secrets-across-namespaces.md similarity index 100% rename from content/docs/tutorials/syncing-secrets-across-namespaces.md rename to content/docs/devops-tips/syncing-secrets-across-namespaces.md diff --git a/content/docs/installation/uninstall.md b/content/docs/installation/uninstall.md index de06fbc9e7..404fb4b8e6 100644 --- a/content/docs/installation/uninstall.md +++ b/content/docs/installation/uninstall.md @@ -11,4 +11,4 @@ cert-manager to go to the relevant uninstall documentation. - [kubectl](./kubectl.md#uninstalling) - [helm](./helm.md#uninstalling) -If you need to preserve cert-manager custom resources (`Certificate`s, `Issuer`s etc), that are not version controlled or backed up by other means, take a look at our [backup and restore guide](../tutorials/backup.md). +If you need to preserve cert-manager custom resources (`Certificate`s, `Issuer`s etc), that are not version controlled or backed up by other means, take a look at our [backup and restore guide](../devops-tips/backup.md). diff --git a/content/docs/installation/upgrading/README.md b/content/docs/installation/upgrading/README.md index 16d60fb1c9..f29e0a7c5d 100644 --- a/content/docs/installation/upgrading/README.md +++ b/content/docs/installation/upgrading/README.md @@ -10,7 +10,7 @@ versions, and information on things to look out for when upgrading. > Note: Before performing upgrades of cert-manager, it is advised to take a > backup of all your cert-manager resources just in case an issue occurs whilst > upgrading. You can read how to backup and restore cert-manager in the [backup -> and restore](../../tutorials/backup.md) guide. +> and restore](../../devops-tips/backup.md) guide. We recommend that you upgrade cert-manager one minor version at a time, always choosing the latest patch version for the minor version. You should always read diff --git a/content/docs/installation/upgrading/upgrading-0.10-0.11.md b/content/docs/installation/upgrading/upgrading-0.10-0.11.md index d79aea493f..81a85b03d1 100644 --- a/content/docs/installation/upgrading/upgrading-0.10-0.11.md +++ b/content/docs/installation/upgrading/upgrading-0.10-0.11.md @@ -20,7 +20,7 @@ resources, will need to be updated to reflect these changes. This upgrade should be performed in a few steps: 1. Back up existing cert-manager resources, as per the - [backup and restore guide](../../tutorials/backup.md). + [backup and restore guide](../../devops-tips/backup.md). 2. [Uninstall cert-manager](../uninstall.md). diff --git a/content/docs/installation/upgrading/upgrading-0.16-1.0.md b/content/docs/installation/upgrading/upgrading-0.16-1.0.md index 2358ec40a3..b676bdea2d 100644 --- a/content/docs/installation/upgrading/upgrading-0.16-1.0.md +++ b/content/docs/installation/upgrading/upgrading-0.16-1.0.md @@ -48,7 +48,7 @@ However you should convert the (Cluster)Issuers and delete the old CRD versions. This upgrade MUST be performed in the following sequence of steps: -1. [Back up](../../tutorials/backup.md) existing cert-manager resources. See the backup section. +1. [Back up](../../devops-tips/backup.md) existing cert-manager resources. See the backup section. 2. [Uninstall cert-manager](../uninstall.md). diff --git a/content/docs/installation/upgrading/upgrading-0.5-0.6.md b/content/docs/installation/upgrading/upgrading-0.5-0.6.md index ab9a92770c..2b1fa457bb 100644 --- a/content/docs/installation/upgrading/upgrading-0.5-0.6.md +++ b/content/docs/installation/upgrading/upgrading-0.5-0.6.md @@ -50,7 +50,7 @@ This **will not** delete the Secret resources being used by your apps. Before upgrading you will need to: -1. Read and follow the [backup guide](../../tutorials/backup.md) to create a +1. Read and follow the [backup guide](../../devops-tips/backup.md) to create a backup of your configuration. 2. Delete the existing cert-manager Helm release (replacing 'cert-manager' with @@ -91,7 +91,7 @@ $ helm install \ stable/cert-manager ``` -4. Follow the steps in the [restore guide](../../tutorials/backup.md) to +4. Follow the steps in the [restore guide](../../devops-tips/backup.md) to restore your configuration. 5. Verify that your Issuers and Certificate resources are 'Ready': diff --git a/content/docs/installation/upgrading/upgrading-0.8-0.9.md b/content/docs/installation/upgrading/upgrading-0.8-0.9.md index 3aa60c22c0..b28ebc9d4f 100644 --- a/content/docs/installation/upgrading/upgrading-0.8-0.9.md +++ b/content/docs/installation/upgrading/upgrading-0.8-0.9.md @@ -8,7 +8,7 @@ Due to a change in the API group that cert-manager deployments use before applying the new version. This will cause downtime until the new version has been applied. No data loss will occur during this operation however it is always advised to backup your data during an upgrade, which you can follow -[here](../../tutorials/backup.md). To perform this action run: +[here](../../devops-tips/backup.md). To perform this action run: ```bash $ kubectl delete deployments --namespace cert-manager \ diff --git a/content/docs/installation/upgrading/upgrading-1.1-1.2.md b/content/docs/installation/upgrading/upgrading-1.1-1.2.md index b97e85b9d7..ab7a4e20e1 100644 --- a/content/docs/installation/upgrading/upgrading-1.1-1.2.md +++ b/content/docs/installation/upgrading/upgrading-1.1-1.2.md @@ -7,7 +7,7 @@ In an effort to introduce new features whilst keeping the project maintainable, cert-manager now only supports Kubernetes down to version `v1.16`. This means the `legacy` manifests have now been removed. Some users experience issues when upgrading the legacy `CRD`s to `v1.2`. To solve this, you could replace the `CRD`s: -1. Backup `cert-manager` resources as described in [the docs](../../tutorials/backup.md) +1. Backup `cert-manager` resources as described in [the docs](../../devops-tips/backup.md) 2. Run `kubectl replace -f https://github.com/cert-manager/cert-manager/releases/download/v1.2.0/cert-manager.crds.yaml` to replace the CRDs. 3. Follow the standard upgrade process. You can read more about supported Kubernetes versions diff --git a/content/docs/manifest.json b/content/docs/manifest.json index c945ef8c6b..57c54f508f 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -13,7 +13,7 @@ "path": "/docs/getting-started/README.md" }, { - "title": "Installation", + "title": "0. Installation", "routes": [ { "title": "Introduction", @@ -28,29 +28,33 @@ "path": "/docs/installation/compatibility.md" }, { - "title": "kubectl apply", + "title": "a. kubectl apply", "path": "/docs/installation/kubectl.md" }, { - "title": "Helm", + "title": "b. Helm", "path": "/docs/installation/helm.md" }, { - "title": "OperatorHub (OLM)", + "title": "c. OperatorHub (OLM)", "path": "/docs/installation/operator-lifecycle-manager.md" }, { - "title": "Other tools", + "title": "d. Other tools", "path": "/docs/installation/other-tools.md" }, - { - "title": "Verifying", - "path": "/docs/installation/verify.md" - }, { "title": "Feature flags", "path": "/docs/installation/featureflags.md" }, + { + "title": "Best Practice", + "path": "/docs/installation/best-practice.md" + }, + { + "title": "Verify installation", + "path": "/docs/installation/verify.md" + }, { "title": "Upgrading", "routes": [ @@ -191,15 +195,11 @@ { "title": "Signature Verification", "path": "/docs/installation/code-signing.md" - }, - { - "title": "Best Practice", - "path": "/docs/installation/best-practice.md" } ] }, { - "title": "Configuring Issuers", + "title": "1. Configuring Issuers", "routes": [ { "title": "Introduction", @@ -295,7 +295,7 @@ ] }, { - "title": "Requesting Certificates", + "title": "2. Requesting Certificates", "routes": [ { "title": "Introduction", @@ -305,10 +305,6 @@ "title": "Certificate Resources", "path": "/docs/usage/certificate.md" }, - { - "title": "Prometheus Metrics", - "path": "/docs/usage/prometheus-metrics.md" - }, { "title": "Securing Ingress Resources", "path": "/docs/usage/ingress.md" @@ -317,68 +313,96 @@ "title": "Securing Gateway Resources", "path": "/docs/usage/gateway.md" }, - { - "title": "Securing Istio Service Mesh", - "path": "/docs/usage/istio.md" - }, - { - "title": "CSI Driver", - "path": "/docs/usage/csi.md" - }, { "title": "Kubernetes CertificateSigningRequests", "path": "/docs/usage/kube-csr.md" }, { - "title": "Policy for cert-manager certificates", - "path": "/docs/usage/approver-policy.md" + "title": "Service Mesh", + "routes": [ + { + "title": "istio-csr", + "path": "/docs/usage/istio-csr.md" + } + ] + }, + { + "title": "CSI Driver", + "routes": [ + { + "title": "Introduction", + "path": "/docs/usage/csi.md" + }, + { + "title": "csi-driver", + "path": "/docs/usage/csi-driver.md" + }, + { + "title": "csi-driver-spiffe", + "path": "/docs/usage/csi-driver-spiffe.md" + } + ] } ] }, { - "title": "Projects", + "title": "3. Distributing Trust", "routes": [ { - "title": "Contents", - "path": "/docs/projects/README.md" - }, - { - "title": "istio-csr", - "path": "/docs/projects/istio-csr.md" - }, - { - "title": "csi-driver", - "path": "/docs/projects/csi-driver.md" - }, - { - "title": "csi-driver-spiffe", - "path": "/docs/projects/csi-driver-spiffe.md" + "title": "Introduction", + "path": "/docs/trust/README.md" }, { - "title": "approver-policy", + "title": "trust-manager", "routes": [ { "title": "Introduction", - "path": "/docs/projects/approver-policy/README.md" + "path": "/docs/trust/trust-manager/README.md" }, { "title": "API Reference", - "path": "/docs/projects/approver-policy/api-reference.md" + "path": "/docs/trust/trust-manager/api-reference.md" } ] + } + ] + }, + { + "title": "4. Defining Policy", + "routes": [ + { + "title": "Introduction", + "path": "/docs/policy/README.md" }, { - "title": "trust-manager", + "title": "Defaulting", + "path": "/docs/policy/defaulting.md" + }, + { + "title": "Approval", "routes": [ { "title": "Introduction", - "path": "/docs/projects/trust-manager/README.md" + "path": "/docs/policy/approval/README.md" }, { - "title": "API Reference", - "path": "/docs/projects/trust-manager/api-reference.md" + "title": "approver-policy", + "routes": [ + { + "title": "Introduction", + "path": "/docs/policy/approval/approver-policy/README.md" + }, + { + "title": "API Reference", + "path": "/docs/policy/approval/approver-policy/api-reference.md" + } + ] } ] + }, + { + "title": "Issuing", + "path": "/docs/policy/issuing.md" } ] }, @@ -405,10 +429,6 @@ "title": "Migrating from Kube-LEGO", "path": "/docs/tutorials/acme/migrating-from-kube-lego.md" }, - { - "title": "Backup and Restore Resources", - "path": "/docs/tutorials/backup.md" - }, { "title": "DNS Validation", "path": "/docs/tutorials/acme/dns-validation.md" @@ -429,10 +449,6 @@ "title": "Securing the istio Service Mesh using cert-manager", "path": "/docs/tutorials/istio-csr/istio-csr.md" }, - { - "title": "Syncing Secrets Across Namespaces", - "path": "/docs/tutorials/syncing-secrets-across-namespaces.md" - }, { "title": "Securing Ingresses with ZeroSSL", "path": "/docs/tutorials/zerossl/zerossl.md" @@ -444,12 +460,33 @@ ] }, { - "title": "Troubleshooting", + "title": "DevOps Tips", + "routes": [ + { + "title": "Prometheus Metrics", + "path": "/docs/devops-tips/prometheus-metrics.md" + }, + { + "title": "Backup and Restore Resources", + "path": "/docs/devops-tips/backup.md" + }, + { + "title": "Syncing Secrets Across Namespaces", + "path": "/docs/devops-tips/syncing-secrets-across-namespaces.md" + } + ] + }, + { + "title": "Troubleshooting & FAQ", "routes": [ { "title": "Introduction", "path": "/docs/troubleshooting/README.md" }, + { + "title": "Frequently Asked Questions", + "path": "/docs/faq/README.md" + }, { "title": "Troubleshooting ACME / Let's Encrypt Certificates", "path": "/docs/troubleshooting/acme.md" @@ -460,10 +497,6 @@ } ] }, - { - "title": "FAQ", - "path": "/docs/faq/README.md" - }, { "title": "Contributing", "routes": [ @@ -471,6 +504,10 @@ "title": "Introduction", "path": "/docs/contributing/README.md" }, + { + "title": "Projects", + "path": "/docs/contributing/projects.md" + }, { "title": "Feature Policy", "path": "/docs/contributing/policy.md" @@ -688,39 +725,6 @@ } ] }, - { - "title": "Concepts", - "routes": [ - { - "title": "Introduction", - "path": "/docs/concepts/README.md" - }, - { - "title": "Issuer", - "path": "/docs/concepts/issuer.md" - }, - { - "title": "Certificate", - "path": "/docs/concepts/certificate.md" - }, - { - "title": "CertificateRequest", - "path": "/docs/concepts/certificaterequest.md" - }, - { - "title": "ACME Orders and Challenges", - "path": "/docs/concepts/acme-orders-challenges.md" - }, - { - "title": "Webhook", - "path": "/docs/concepts/webhook.md" - }, - { - "title": "CA Injector", - "path": "/docs/concepts/ca-injector.md" - } - ] - }, { "title": "Reference", "routes": [ @@ -769,6 +773,40 @@ { "title": "API Reference", "path": "/docs/reference/api-docs.md" + }, + + { + "title": "Concepts", + "routes": [ + { + "title": "Introduction", + "path": "/docs/concepts/README.md" + }, + { + "title": "Issuer", + "path": "/docs/concepts/issuer.md" + }, + { + "title": "Certificate", + "path": "/docs/concepts/certificate.md" + }, + { + "title": "CertificateRequest", + "path": "/docs/concepts/certificaterequest.md" + }, + { + "title": "ACME Orders and Challenges", + "path": "/docs/concepts/acme-orders-challenges.md" + }, + { + "title": "Webhook", + "path": "/docs/concepts/webhook.md" + }, + { + "title": "CA Injector", + "path": "/docs/concepts/ca-injector.md" + } + ] } ] } diff --git a/content/docs/policy/README.md b/content/docs/policy/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/content/docs/usage/approver-policy.md b/content/docs/policy/approval/README.md similarity index 65% rename from content/docs/usage/approver-policy.md rename to content/docs/policy/approval/README.md index b712d04b7f..87eaabcae6 100644 --- a/content/docs/usage/approver-policy.md +++ b/content/docs/policy/approval/README.md @@ -3,12 +3,12 @@ title: Policy for cert-manager certificates description: 'cert-manager usage: approver-policy' --- -cert-manager [CertificateRequests](../concepts/certificaterequest.md) can be +cert-manager [CertificateRequests](../../concepts/certificaterequest.md) can be rejected from being signed by using the [approval -API](../concepts/certificaterequest.md#approval). +API](../../concepts/certificaterequest.md#approval). [approver-policy](https://github.com/cert-manager/approver-policy) is a cert-manager project that enables you to write policy to automatically manage this approval mechanism. -Please read the [project page](../projects/approver-policy/README.md) for more +Please read the [project page](approver-policy/README.md) for more information on how to install and use approver-policy. diff --git a/content/docs/projects/approver-policy/README.md b/content/docs/policy/approval/approver-policy/README.md similarity index 97% rename from content/docs/projects/approver-policy/README.md rename to content/docs/policy/approval/approver-policy/README.md index bdad17d829..aa6084f2ab 100644 --- a/content/docs/projects/approver-policy/README.md +++ b/content/docs/policy/approval/approver-policy/README.md @@ -4,14 +4,14 @@ description: 'Policy plugin for cert-manager' --- approver-policy is a cert-manager -[approver](../../concepts/certificaterequest.md#approval) +[approver](../../../concepts/certificaterequest.md#approval) that will approve or deny CertificateRequests based on policies defined in the `CertificateRequestPolicy` custom resource. ## Prerequisites -[cert-manager must be installed](../../installation/README.md), and -the [the default approver in cert-manager must be disabled](../../concepts/certificaterequest.md#approver-controller). +[cert-manager must be installed](../../../installation/README.md), and +the [the default approver in cert-manager must be disabled](../../../concepts/certificaterequest.md#approver-controller). > ⚠️ If the default approver is not disabled in cert-manager, approver-policy will > race with cert-manager and policy will be ineffective. @@ -66,10 +66,10 @@ helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manag ``` If you are using approver-policy with [external -issuers](../../configuration/external.md), you _must_ +issuers](../../../configuration/external.md), you _must_ include their signer names so that approver-policy has permissions to approve and deny CertificateRequests that -[reference them](../../concepts/certificaterequest.md#rbac-syntax). +[reference them](../../../concepts/certificaterequest.md#rbac-syntax). For example, if using approver-policy for the internal issuer types, along with [google-cas-issuer](https://github.com/jetstack/google-cas-issuer), and [aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer), @@ -104,7 +104,7 @@ request, the request is denied.** A denied CertificateRequest is considered to be permanently failed. If it was created for a Certificate resource, the issuance will be retried with -[exponential backoff](../../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) +[exponential backoff](../../../faq/README.md#what-happens-if-issuance-fails-will-it-be-retried) like all other permanent issuance failures. A CertificateRequest that is neither approved nor denied (because no matching policy was found) will not be further processed by cert-manager until it gets either approved or denied. diff --git a/content/docs/projects/approver-policy/api-reference.md b/content/docs/policy/approval/approver-policy/api-reference.md similarity index 100% rename from content/docs/projects/approver-policy/api-reference.md rename to content/docs/policy/approval/approver-policy/api-reference.md diff --git a/content/docs/policy/issuing.md b/content/docs/policy/issuing.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/content/docs/trust/README.md b/content/docs/trust/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/content/docs/projects/trust-manager/README.md b/content/docs/trust/trust-manager/README.md similarity index 99% rename from content/docs/projects/trust-manager/README.md rename to content/docs/trust/trust-manager/README.md index 7507585c4b..b3a88c3547 100644 --- a/content/docs/projects/trust-manager/README.md +++ b/content/docs/trust/trust-manager/README.md @@ -134,7 +134,7 @@ helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --wait ### approver-policy Integration -If you're running [approver-policy](../approver-policy/README.md) then cert-manager's default approver will be disabled which will mean that +If you're running [approver-policy](../../policy/approval/approver-policy/README.md) then cert-manager's default approver will be disabled which will mean that trust-manager's webhook certificate will - by default - block when you install the Helm chart until it's manually approved. As of trust-manager v0.6.0 you can choose to automatically add an approver-policy `CertificateRequestPolicy` which diff --git a/content/docs/projects/trust-manager/api-reference.md b/content/docs/trust/trust-manager/api-reference.md similarity index 100% rename from content/docs/projects/trust-manager/api-reference.md rename to content/docs/trust/trust-manager/api-reference.md diff --git a/content/docs/tutorials/README.md b/content/docs/tutorials/README.md index cc839b6b10..b286d96594 100644 --- a/content/docs/tutorials/README.md +++ b/content/docs/tutorials/README.md @@ -12,8 +12,6 @@ for you to learn from. Take a look! Learn how to deploy cert-manager on Google Kubernetes Engine and how to configure it to get certificates for Ingress, from Let's Encrypt. - [AKS + LoadBalancer + Let's Encrypt](getting-started-aks-letsencrypt/README.md): Learn how to deploy cert-manager on Azure Kubernetes Service (AKS) and how to configure it to get certificates for an HTTPS web server, from Let's Encrypt. -- [Backup and Restore Resources](./backup.md): Backup the cert-manager resources - in your cluster and then restore them. - [Pomerium Ingress](./acme/pomerium-ingress.md): Tutorial on using the Pomerium Ingress Controller with cert-manager. - [Issuing an ACME Certificate using DNS Validation](./acme/dns-validation.md): Tutorial on how to resolve DNS ownership validation using DNS01 challenges. @@ -26,8 +24,6 @@ for you to learn from. Take a look! certificate. - [Securing an Istio service mesh with cert-manager](./istio-csr/istio-csr.md): Tutorial for securing an Istio service mesh using a cert-manager issuer. -- [Syncing Secrets Across Namespaces](./syncing-secrets-across-namespaces.md): - Learn how to synchronize Kubernetes Secret resources across namespaces using extensions such as: reflector, kubed and kubernetes-replicator. - [Obtaining SSL certificates with the ZeroSSL](./zerossl/zerossl.md): Tutorial describing usage of the ZeroSSL as external ACME server. ### External Tutorials diff --git a/content/docs/tutorials/getting-started-with-trust-manager/README.md b/content/docs/tutorials/getting-started-with-trust-manager/README.md index 5ce09cbc69..afeec714f1 100644 --- a/content/docs/tutorials/getting-started-with-trust-manager/README.md +++ b/content/docs/tutorials/getting-started-with-trust-manager/README.md @@ -6,7 +6,7 @@ description: Learn how to deploy and configure trust-manager to automatically di *Last Verified: 19 June 2023* In this tutorial we will walk through how we can use -[trust-manager](https://cert-manager.io/docs/projects/trust-manager/) to +[trust-manager](https://cert-manager.io/docs/trust/trust-manager/) to distribute publicly trusted Certificate Authority (CA) certificates inside a Kubernetes cluster. Once distributed we will also show: @@ -43,7 +43,7 @@ demo namespace: `team-a`. ### Setup Application & Bundle 1) Install trust-manager following the - [instructions here](../../projects/trust-manager/README.md#installation). + [instructions here](../../trust/trust-manager/README.md#installation). 1) Create your first `Bundle` resource including only Public CA certificates diff --git a/content/docs/tutorials/istio-csr/istio-csr.md b/content/docs/tutorials/istio-csr/istio-csr.md index 32c5c1ae3e..bf1f292b09 100644 --- a/content/docs/tutorials/istio-csr/istio-csr.md +++ b/content/docs/tutorials/istio-csr/istio-csr.md @@ -78,7 +78,7 @@ kubectl create secret generic -n cert-manager istio-root-ca --from-file=ca.pem=c ## Installing istio-csr istio-csr is best installed via Helm, and it should be simple and quick to install. There -are a bunch of other configuration options for the helm chart, which you can check out [here](../../projects/istio-csr.md). +are a bunch of other configuration options for the helm chart, which you can check out [here](../../usage/istio-csr.md). ```console helm repo add jetstack https://charts.jetstack.io diff --git a/content/docs/usage/README.md b/content/docs/usage/README.md index f02af619dc..c12c16bec2 100644 --- a/content/docs/usage/README.md +++ b/content/docs/usage/README.md @@ -23,9 +23,6 @@ There are several use cases and methods for requesting certificates through cert pods. - [Securing Istio Gateway](https://istio.io/docs/tasks/traffic-management/ingress/ingress-certmgr/): Secure your Istio Gateway in Kubernetes using cert-manager. -- [Securing Istio Service Mesh](./istio.md): Using the cert-manager +- [Securing Istio Service Mesh](./istio-csr.md): Using the cert-manager [Istio](https://istio.io) integration, secure the mTLS PKI for each pod through cert-manager managed certificates. -- [Policy for cert-manager certificates](./approver-policy.md): Manage - what cert-manager certificates are able to be signed or rejected through - custom resource defined policy. diff --git a/content/docs/projects/csi-driver-spiffe.md b/content/docs/usage/csi-driver-spiffe.md similarity index 100% rename from content/docs/projects/csi-driver-spiffe.md rename to content/docs/usage/csi-driver-spiffe.md diff --git a/content/docs/projects/csi-driver.md b/content/docs/usage/csi-driver.md similarity index 99% rename from content/docs/projects/csi-driver.md rename to content/docs/usage/csi-driver.md index 9bd10b2dad..f241e7bb9b 100644 --- a/content/docs/projects/csi-driver.md +++ b/content/docs/usage/csi-driver.md @@ -184,7 +184,7 @@ volumeAttributes: If the flag `--use-token-request` is enabled on the csi-driver DaemonSet, the [CertificateRequest](../concepts/certificaterequest.md) resource will be created by the mounting Pod's ServiceAccount. This can be paired with -[approver-policy](./approver-policy/README.md) to enable advanced policy control +[approver-policy](../policy/approval/approver-policy/README.md) to enable advanced policy control on a per-ServiceAccount basis. Ensure that you give permissions to Pod ServiceAccounts to create CertificateRequests diff --git a/content/docs/usage/csi.md b/content/docs/usage/csi.md index 5e1a6124b0..ff91778eed 100644 --- a/content/docs/usage/csi.md +++ b/content/docs/usage/csi.md @@ -6,7 +6,7 @@ description: 'cert-manager usage: CSI driver' ## Enabling mTLS of Pods using the cert-manager CSI Driver A [Container Storage Interface (CSI) -driver](../projects/csi-driver.md) has been created to +driver](csi-driver.md) has been created to facilitate mTLS of Pods running inside your cluster through use of cert-manager. Using this driver will ensure that the private key and corresponding signed certificate will be unique to each Pod and will be stored on disk to the node diff --git a/content/docs/projects/istio-csr.md b/content/docs/usage/istio-csr.md similarity index 92% rename from content/docs/projects/istio-csr.md rename to content/docs/usage/istio-csr.md index 57748517bc..19ae2eb4bd 100644 --- a/content/docs/projects/istio-csr.md +++ b/content/docs/usage/istio-csr.md @@ -1,8 +1,13 @@ --- -title: istio-csr -description: '' +title: Securing Istio Service Mesh +description: 'cert-manager usage: Istio and istio-csr' --- +cert-manager can be integrated with [Istio](https://istio.io) using the project +[istio-csr](https://github.com/cert-manager/istio-csr). istio-csr will deploy an +agent that is responsible for receiving certificate signing requests for all +members of the Istio mesh, and signing them through cert-manager. + istio-csr is an agent that allows for [Istio](https://istio.io) workload and control plane components to be secured using [cert-manager](https://cert-manager.io). diff --git a/content/docs/usage/istio.md b/content/docs/usage/istio.md deleted file mode 100644 index 86149ae22b..0000000000 --- a/content/docs/usage/istio.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Securing Istio Service Mesh -description: 'cert-manager usage: Istio and istio-csr' ---- - -cert-manager can be integrated with [Istio](https://istio.io) using the project -[istio-csr](https://github.com/cert-manager/istio-csr). istio-csr will deploy an -agent that is responsible for receiving certificate signing requests for all -members of the Istio mesh, and signing them through cert-manager. - -[istio-csr](https://github.com/cert-manager/istio-csr) will sign all control -plane and workload certificates via your chosen cert-manager Issuer. - ---- - -Please follow the instructions for installing and using istio-csr on the -[project page](../projects/istio-csr.md). diff --git a/package.json b/package.json index 55a5892a89..5d69a25abf 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "check": "concurrently --group --timings npm:check:* # Run all the npm check:* scripts in parallel", "check:next-lint": "next lint", "check:links": "find content/docs -type f -name '*.md' | xargs markdown-link-check --quiet --config markdown-link-check.json 2>&1 | awk -v RS=FILE: '/ERROR/{f=1; print RS $0} END{exit f}' # Split into records based on the word FILE and print only records containing word ERROR", - "check:spelling": "FORCE_COLOR=1 mdspell --report --en-us --ignore-numbers --ignore-acronyms 'content/**/*.md' 'content/**/*.html' '_layouts/*.html' '_includes/*.html' '*.html' '!**/api-docs.md' '!content/*docs/projects/approver-policy/api-reference.md' # Force color output in mdspell. # See https://github.com/lukeapage/node-markdown-spellcheck/issues/36#issuecomment-482649408 ", + "check:spelling": "FORCE_COLOR=1 mdspell --report --en-us --ignore-numbers --ignore-acronyms 'content/**/*.md' 'content/**/*.html' '_layouts/*.html' '_includes/*.html' '*.html' '!**/api-docs.md' '!content/*docs/policy/approval/approver-policy/api-reference.md' # Force color output in mdspell. # See https://github.com/lukeapage/node-markdown-spellcheck/issues/36#issuecomment-482649408 ", "check:markdown": "remark --rc-path .remarkrc --frail --quiet content/" }, "lint-staged": { diff --git a/public/_redirects b/public/_redirects index 37129ed5b9..1b7c6b607e 100644 --- a/public/_redirects +++ b/public/_redirects @@ -187,4 +187,19 @@ https://docs.cert-manager.io/* https://cert-manager.io/docs/:splat 302! /docs/installation/cmctl/ /docs/reference/cmctl/ 301! # Rename trust to trust-manager -/docs/projects/trust/ /docs/projects/trust-manager/ 301! +/docs/projects/trust/ /docs/trust/trust-manager/ 301! + +# Moved pages to new devops section +/docs/usage/prometheus-metrics/ /docs/devops-tips/prometheus-metrics/ 301! +/docs/tutorials/backup/ /docs/devops-tips/backup/ 301! +/docs/tutorials/syncing-secrets-across-namespaces/ /docs/devops-tips/syncing-secrets-across-namespaces/ 301! + +# Integrating the project pages into the main website +/docs/usage/istio/ /docs/usage/istio-csr/ 301! +/docs/projects/istio-csr/ /docs/usage/istio-csr/ 301! +/docs/projects/csi-driver/ /docs/usage/csi-driver/ 301! +/docs/projects/csi-driver-spiffe/ /docs/usage/csi-driver-spiffe/ 301! +/docs/trust/trust-manager/ /docs/trust/trust-manager/ 301! +/docs/trust/trust-manager/api-reference/ /docs/trust/trust-manager/api-reference/ 301! +/docs/projects/approver-policy/ /docs/policy/approval/approver-policy/ 301! +/docs/projects/approver-policy/api-reference/ /docs/policy/approval/approver-policy/api-reference/ 301! diff --git a/scripts/gendocs/generate-trust-manager b/scripts/gendocs/generate-trust-manager index 3d6a6d6dfb..e4ee7cbb40 100755 --- a/scripts/gendocs/generate-trust-manager +++ b/scripts/gendocs/generate-trust-manager @@ -61,4 +61,4 @@ git clone "https://github.com/cert-manager/trust-manager.git" "$tmpdir" checkout "v0.6.0" -gendocs "$REPO_ROOT/content/docs/projects/trust-manager/api-reference.md" +gendocs "$REPO_ROOT/content/docs/trust/trust-manager/api-reference.md" From cb45306f488ed04d2ac792dfa3da1b7042255171 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 28 Sep 2023 19:45:39 +0200 Subject: [PATCH 172/264] write documentation for the different kinds of policy Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/policy/README.md | 20 ++++++++++++ content/docs/policy/approval/README.md | 42 +++++++++++++++++++------ content/docs/policy/defaulting.md | 41 ++++++++++++++++++++++++ content/docs/policy/issuing.md | 16 ++++++++++ public/images/issuance-flow-policy.png | Bin 0 -> 58036 bytes 5 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 content/docs/policy/defaulting.md create mode 100644 public/images/issuance-flow-policy.png diff --git a/content/docs/policy/README.md b/content/docs/policy/README.md index e69de29bb2..c9529fdfbb 100644 --- a/content/docs/policy/README.md +++ b/content/docs/policy/README.md @@ -0,0 +1,20 @@ +--- +title: Policy +description: 'Rules for guiding certificate self-service.' +--- + +![issuance flow: policy](/images/issuance-flow-policy.png) + +To scale certificate management, it is critical to distribute the responsibility of +managing certificates to the teams that need them. To do this effectively, often a +self-service model is used, teams are considered "customers" of a platform team that +provides the Kubernetes infrastructure (including certificate management). This multi-tenant +model removes the bottleneck of a single team managing certificates for everyone. + +However, to be successful, the platform team should guide its customers to follow best +practices, and ensure that certificates are issued in a secure and consistent way. These +best practices and rules are what we call "policy". Currently, cert-manager distinguishes +between three types of policy: +- [Defaulting Policy](defaulting.md): Defining defaults for Certificate properties. +- [Approval Policy](approval): Restricting who can request which certificates. +- [Issuing Policy](issuing.md): Configuring how requests are mapped to Certificates. diff --git a/content/docs/policy/approval/README.md b/content/docs/policy/approval/README.md index 87eaabcae6..bbcb44bd5b 100644 --- a/content/docs/policy/approval/README.md +++ b/content/docs/policy/approval/README.md @@ -1,14 +1,36 @@ --- -title: Policy for cert-manager certificates -description: 'cert-manager usage: approver-policy' +title: Approval Policy +description: 'Restricting who can request which certificates.' --- -cert-manager [CertificateRequests](../../concepts/certificaterequest.md) can be -rejected from being signed by using the [approval -API](../../concepts/certificaterequest.md#approval). -[approver-policy](https://github.com/cert-manager/approver-policy) is a -cert-manager project that enables you to write policy to automatically manage -this approval mechanism. +![issuance flow: policy](/images/issuance-flow-policy.png) -Please read the [project page](approver-policy/README.md) for more -information on how to install and use approver-policy. +In the issuance flow, there are typically two places where non-conforming certificate +requests can be rejected: before sending the X.509 CertificateSigningRequest to the +issuer, and after receiving the X.509 Certificate by the issuer. In the first case, +it is cert-manager that rejects the request. In the second case, it is the issuer +that rejects the request. + +## Rejecting requests before sending the X.509 CertificateSigningRequest to the issuer + +cert-manager requires that a [CertificateRequest](../../concepts/certificaterequest.md) +is approved before it is sent to the issuer. Also, CertificateSigningRequests must +be approved before they are sent to the issuer. This approval is done by adding an +[approval condition](../../concepts/certificaterequest.md#approval) to the resource. + +In a default installation, cert-manager automatically approves all CertificateRequests +and CertificateSigningRequests that use any of its built-in issuers. This is done +simplify the first-time experience of using cert-manager. However, this is not +recommended for production environments. Instead, you should configure a more strict +auto-approver that limits who can request which certificates. [approver-policy](approver-policy) +is an example of such an auto-approver. + +## Rejecting requests after receiving the X.509 CertificateSigningRequest by the issuer + +After receiving the X.509 CertificateSigningRequest, the logic to reject requests +is up to the issuer. cert-manager supports a large number of issuers, each issuer +has full autonomy over what requests are rejected and what error messages are returned. +Additionally, an issuer could also choose to not reject any requests and instead +issue a certificate that does not include non-conforming properties. More generally, +the issuer is free to use any logic to map the properties in the X.509 CertificateSigningRequest +to the properties in the X.509 Certificate (see [Issuing Policy](../issuing.md). diff --git a/content/docs/policy/defaulting.md b/content/docs/policy/defaulting.md new file mode 100644 index 0000000000..1bb5a7fdb2 --- /dev/null +++ b/content/docs/policy/defaulting.md @@ -0,0 +1,41 @@ +--- +title: Defaulting Policy +description: 'Defining defaults for Certificate properties.' +--- + +![issuance flow: policy](/images/issuance-flow-policy.png) + +In the issuance flow, there are two places where defaults can be applied: before the X.509 +CertificateSigningRequest is created, and before the X.509 Certificate is created. Only in +the first case, it is cert-manager that applies the defaults. In the second case, it is the +issuer that applies the defaults. + +An important difference between these two cases is that in the first case, there are more +properties that can be defaulted. For example, since the private key still has to be generated, +all the properties of the private key can be defaulted. In the second case, the private key +has already been generated. Also, the issuer reference itself can be defaulted in the first +case, but not in the second case. For example, in a specific namespace, the issuer can be +defaulted to a specific issuer. + +Defaulting is done to simplify the experience of the person requesting the certificate. It +does not prevent the person from overriding the defaults. Therefore, an approval policy can +be used (see [Approval Policy](approval) for more details). + +## Defaults applied by cert-manager: before creating the X.509 CertificateSigningRequest + +To apply defaults before the X.509 CertificateSigningRequest is created, defaults must be +applied to the inputs used to create the CertificateSigningRequest. After the CertificateSigningRequest +is created, it cannot be modified without invalidating the its signature. This means that +defaults cannot be applied to the CertificateRequest or CertificateSigningRequest resource itself. + +Instead, defaults must be applied to the Certificate resource that is used to create the CertificateSigningRequest. +When using a CSI driver, defaults must be applied to CSI annotations or CSI driver configuration. +To dynamically apply defaults to these resources, you can use tools like [`kyverno`](https://kyverno.io/). +CI/CD tools like ArgoCD can also be used to apply defaults to these resources. + +## Defaults applied by the issuer: before creating the X.509 Certificate + +Before creating the X.509 Certificate, the issuer can use default values for properties in +the resulting certificate. More generally, the issuer is free to use any logic to map the +properties in the X.509 CertificateSigningRequest to the properties in the X.509 Certificate +(see [Issuing Policy](issuing.md) for more details). diff --git a/content/docs/policy/issuing.md b/content/docs/policy/issuing.md index e69de29bb2..973c761f0a 100644 --- a/content/docs/policy/issuing.md +++ b/content/docs/policy/issuing.md @@ -0,0 +1,16 @@ +--- +title: Issuing Policy +description: 'Configuring how requests are mapped to Certificates.' +--- + +![issuance flow: policy](/images/issuance-flow-policy.png) + +cert-manager integrates with a large number of different certificate issuers, each +issuer has full autonomy over what issued certificates look like and what properties +they have. cert-manager does not require or enforce any specific relationship between +the properties in a CertificateRequest/ CertificateSigningRequest and the properties +in the issued certificate (except for the public key which must match). + +For the core [SelfSigned](../configuration/selfsigned.md) and [CA](../configuration/ca.md) issuers, +cert-manager implements its own issuing policy. This policy is very simple and is not configurable. +All the requested properties will be copied into the issued certificate. diff --git a/public/images/issuance-flow-policy.png b/public/images/issuance-flow-policy.png new file mode 100644 index 0000000000000000000000000000000000000000..efda75ea80abf523d6b126be7e35f34c52dfb20d GIT binary patch literal 58036 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 B>C6BC literal 0 HcmV?d00001 From bfb4cb0defe8d145b681795d97c0a95c5407d3ad Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 28 Sep 2023 20:01:01 +0200 Subject: [PATCH 173/264] explain trust-store management Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/concepts/certificate.md | 25 ----------------------- content/docs/trust/README.md | 30 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/content/docs/concepts/certificate.md b/content/docs/concepts/certificate.md index 3d8ca12198..5b7402f347 100644 --- a/content/docs/concepts/certificate.md +++ b/content/docs/concepts/certificate.md @@ -60,31 +60,6 @@ which contains the following explanation: > assumption that the remote end must already possess it in order to > validate it in any case. -
              - -When configuring a client to connect to a TLS server with a serving certificate that is signed by a private CA, -you will need to provide the client with the CA certificate in order for it to verify the server. -`ca.crt` will likely contain the certificate you need to trust, -but __do not mount the same `Secret` as the server__ to access `ca.crt`. -This is because: - -1. That `Secret` also contains the private key of the server, which should only be accessible to the server. - You should use RBAC to ensure that the `Secret` containing the serving certificate and private key are only accessible to Pods that need it. -2. Rotating CA certificates safely relies on being able to have both the old and new CA certificates trusted at the same time. - By consuming the CA directly from the source, this isn't possible; - you'll be _forced_ to have some down-time in order to rotate certificates. - -When configuring the client you should independently choose and fetch the CA certificates that you want to trust. -Download the CA out of band and store it in a `Secret` or `ConfigMap` separate from the `Secret` containing the server's private key and certificate. - -This ensures that if the material in the `Secret` containing the server key and certificate is tampered with, -the client will fail to connect to the compromised server. - -The same concept also applies when configuring a server for mutually-authenticated TLS; -don't give the server access to Secret containing the client certificate and private key. - -
              - The `dnsNames` field specifies a list of [`Subject Alternative Names`](https://en.wikipedia.org/wiki/Subject_Alternative_Name) to be associated with the certificate. diff --git a/content/docs/trust/README.md b/content/docs/trust/README.md index e69de29bb2..f8c86c1d40 100644 --- a/content/docs/trust/README.md +++ b/content/docs/trust/README.md @@ -0,0 +1,30 @@ +--- +title: Trusting certificates +description: "Managing client trust stores" +--- + +
              + +When configuring a client to connect to a TLS server with a serving certificate that is signed by a private CA, +you will need to provide the client with the CA certificate in order for it to verify the server. +`ca.crt` will likely contain the certificate you need to trust, +but __do not mount the same `Secret` as the server__ to access `ca.crt`. +This is because: + +1. That `Secret` also contains the private key of the server, which should only be accessible to the server. + You should use RBAC to ensure that the `Secret` containing the serving certificate and private key are only accessible to Pods that need it. +2. Rotating CA certificates safely relies on being able to have both the old and new CA certificates trusted at the same time. + By consuming the CA directly from the source, this isn't possible; + you'll be _forced_ to have some down-time in order to rotate certificates. + +
              + +When configuring the client you should independently choose and fetch the CA certificates that you want to trust. +Download the CA out of band and store it in a `Secret` or `ConfigMap` separate from the `Secret` containing the server's private key and certificate. +[trust-manager](trust-manager) can be used to manage these certificates and automatically distribute them to multiple namespaces. + +This ensures that if the material in the `Secret` containing the server key and certificate is tampered with, +the client will fail to connect to the compromised server. + +The same concept also applies when configuring a server for mutually-authenticated TLS; +don't give the server access to Secret containing the client certificate and private key. From ae772de6236bbe666dc48273cc0c8fcbea771846 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 29 Sep 2023 17:03:20 +0200 Subject: [PATCH 174/264] Apply code review suggestion Co-authored-by: Richard Wall Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 1 + content/docs/policy/approval/README.md | 16 ++++++++-------- content/docs/policy/defaulting.md | 16 ++++++++-------- content/docs/policy/issuing.md | 5 +++++ public/_redirects | 4 ++-- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.spelling b/.spelling index 517b2d211f..6a2e900f0f 100644 --- a/.spelling +++ b/.spelling @@ -642,6 +642,7 @@ PrivateCA structs klog relabelings +kustomize FlorianLiebhart cypres diff --git a/content/docs/policy/approval/README.md b/content/docs/policy/approval/README.md index bbcb44bd5b..05f1068f3b 100644 --- a/content/docs/policy/approval/README.md +++ b/content/docs/policy/approval/README.md @@ -6,12 +6,12 @@ description: 'Restricting who can request which certificates.' ![issuance flow: policy](/images/issuance-flow-policy.png) In the issuance flow, there are typically two places where non-conforming certificate -requests can be rejected: before sending the X.509 CertificateSigningRequest to the +requests can be rejected: before sending the X.509 Certificate Signing Request (CSR) to the issuer, and after receiving the X.509 Certificate by the issuer. In the first case, it is cert-manager that rejects the request. In the second case, it is the issuer that rejects the request. -## Rejecting requests before sending the X.509 CertificateSigningRequest to the issuer +## Rejecting requests before sending the X.509 Certificate Signing Request (CSR) to the issuer cert-manager requires that a [CertificateRequest](../../concepts/certificaterequest.md) is approved before it is sent to the issuer. Also, CertificateSigningRequests must @@ -19,18 +19,18 @@ be approved before they are sent to the issuer. This approval is done by adding [approval condition](../../concepts/certificaterequest.md#approval) to the resource. In a default installation, cert-manager automatically approves all CertificateRequests -and CertificateSigningRequests that use any of its built-in issuers. This is done +and CertificateSigningRequests that use any of its built-in issuers. This is done to simplify the first-time experience of using cert-manager. However, this is not recommended for production environments. Instead, you should configure a more strict auto-approver that limits who can request which certificates. [approver-policy](approver-policy) is an example of such an auto-approver. -## Rejecting requests after receiving the X.509 CertificateSigningRequest by the issuer +## Rejecting requests after receiving the X.509 Certificate Signing Request (CSR) by the issuer -After receiving the X.509 CertificateSigningRequest, the logic to reject requests +After receiving the X.509 Certificate Signing Request (CSR), the logic to reject requests is up to the issuer. cert-manager supports a large number of issuers, each issuer has full autonomy over what requests are rejected and what error messages are returned. -Additionally, an issuer could also choose to not reject any requests and instead -issue a certificate that does not include non-conforming properties. More generally, -the issuer is free to use any logic to map the properties in the X.509 CertificateSigningRequest +Additionally, an issuer could also choose accept all requests and instead +override the non-conforming properties in the CSR. More generally, +the issuer is free to use any logic to map the properties in the X.509 Certificate Signing Request (CSR) to the properties in the X.509 Certificate (see [Issuing Policy](../issuing.md). diff --git a/content/docs/policy/defaulting.md b/content/docs/policy/defaulting.md index 1bb5a7fdb2..ee885aa9d4 100644 --- a/content/docs/policy/defaulting.md +++ b/content/docs/policy/defaulting.md @@ -6,7 +6,7 @@ description: 'Defining defaults for Certificate properties.' ![issuance flow: policy](/images/issuance-flow-policy.png) In the issuance flow, there are two places where defaults can be applied: before the X.509 -CertificateSigningRequest is created, and before the X.509 Certificate is created. Only in +CertificateSigningRequest is created, and before the X.509 Certificate is created. In the first case, it is cert-manager that applies the defaults. In the second case, it is the issuer that applies the defaults. @@ -21,21 +21,21 @@ Defaulting is done to simplify the experience of the person requesting the certi does not prevent the person from overriding the defaults. Therefore, an approval policy can be used (see [Approval Policy](approval) for more details). -## Defaults applied by cert-manager: before creating the X.509 CertificateSigningRequest +## Defaults applied by cert-manager: before creating the X.509 Certificate Signing Request (CSR) -To apply defaults before the X.509 CertificateSigningRequest is created, defaults must be -applied to the inputs used to create the CertificateSigningRequest. After the CertificateSigningRequest -is created, it cannot be modified without invalidating the its signature. This means that -defaults cannot be applied to the CertificateRequest or CertificateSigningRequest resource itself. +To apply defaults before the X.509 Certificate Signing Request (CSR) is created, defaults must be +applied to the inputs used to create the CSR. After the CSR is created, it cannot be modified without +invalidating the its signature. This means that defaults cannot be applied to any of the properties of +the CSR included in the CertificateRequest or CertificateSigningRequest resource. Instead, defaults must be applied to the Certificate resource that is used to create the CertificateSigningRequest. When using a CSI driver, defaults must be applied to CSI annotations or CSI driver configuration. To dynamically apply defaults to these resources, you can use tools like [`kyverno`](https://kyverno.io/). -CI/CD tools like ArgoCD can also be used to apply defaults to these resources. +CI/CD tools like Helm, kustomize, ... can also be used to template and apply defaults to these resources. ## Defaults applied by the issuer: before creating the X.509 Certificate Before creating the X.509 Certificate, the issuer can use default values for properties in the resulting certificate. More generally, the issuer is free to use any logic to map the -properties in the X.509 CertificateSigningRequest to the properties in the X.509 Certificate +properties in the X.509 Certificate Signing Request (CSR) to the properties in the X.509 Certificate (see [Issuing Policy](issuing.md) for more details). diff --git a/content/docs/policy/issuing.md b/content/docs/policy/issuing.md index 973c761f0a..b573a5b1a7 100644 --- a/content/docs/policy/issuing.md +++ b/content/docs/policy/issuing.md @@ -14,3 +14,8 @@ in the issued certificate (except for the public key which must match). For the core [SelfSigned](../configuration/selfsigned.md) and [CA](../configuration/ca.md) issuers, cert-manager implements its own issuing policy. This policy is very simple and is not configurable. All the requested properties will be copied into the issued certificate. + +Another example is the [Let's Encrypt CA issuer](https://letsencrypt.org/) which also implements its own issuing policy. This policy is more complex than a 1:1 +copy of the requested properties. For example, the Let's Encrypt issuer will always +[issue certificates with a 90 day validity period](https://letsencrypt.org/2015/11/09/why-90-days). +Also, it only supports a [limited set of (extended) key usages](https://community.letsencrypt.org/t/custom-extendedkeyusage-stripped-from-csr-while-generating-cert/17759/2). \ No newline at end of file diff --git a/public/_redirects b/public/_redirects index 1b7c6b607e..fa12757bca 100644 --- a/public/_redirects +++ b/public/_redirects @@ -199,7 +199,7 @@ https://docs.cert-manager.io/* https://cert-manager.io/docs/:splat 302! /docs/projects/istio-csr/ /docs/usage/istio-csr/ 301! /docs/projects/csi-driver/ /docs/usage/csi-driver/ 301! /docs/projects/csi-driver-spiffe/ /docs/usage/csi-driver-spiffe/ 301! -/docs/trust/trust-manager/ /docs/trust/trust-manager/ 301! -/docs/trust/trust-manager/api-reference/ /docs/trust/trust-manager/api-reference/ 301! +/docs/projects/trust-manager/ /docs/trust/trust-manager/ 301! +/docs/projects/trust-manager/api-reference/ /docs/trust/trust-manager/api-reference/ 301! /docs/projects/approver-policy/ /docs/policy/approval/approver-policy/ 301! /docs/projects/approver-policy/api-reference/ /docs/policy/approval/approver-policy/api-reference/ 301! From 582ff19bfc44d45b52c79abefa30dcc60128a429 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 2 Oct 2023 16:30:23 +0200 Subject: [PATCH 175/264] fix missing logic to unfold side menu when going to the link directly Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- components/docs/Sidebar/Dropdown.jsx | 12 ++++++++---- components/docs/Sidebar/ListItems.jsx | 6 +++--- components/docs/Sidebar/SidebarLink.jsx | 11 ++++++++--- components/docs/Sidebar/index.jsx | 4 ++-- components/docs/VersionSelect.jsx | 8 ++++---- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/components/docs/Sidebar/Dropdown.jsx b/components/docs/Sidebar/Dropdown.jsx index b57ae2d39f..110ef317ed 100644 --- a/components/docs/Sidebar/Dropdown.jsx +++ b/components/docs/Sidebar/Dropdown.jsx @@ -6,9 +6,13 @@ import SidebarLink from './SidebarLink' export default function Dropdown({ routes, parentOpen = true, - setSidebarCollapsed + setParentOpen }) { - const [open, setOpen] = useState(false) + const [open, setSelfOpen] = useState(false) + const setOpen = (v) => { + if (v) setParentOpen(v) + setSelfOpen(v) + } const iconClasses = classNames({ 'block w-4 h-4 transform text-blue-1': true, 'rotate-180': open @@ -41,7 +45,7 @@ export default function Dropdown({ routes={r} parentOpen={open} key={`${r.title}-${idx}`} - setSidebarCollapsed={setSidebarCollapsed} + setParentOpen={setOpen} /> ) @@ -52,7 +56,7 @@ export default function Dropdown({ href={r.path} caption={r.title} parentOpen={open} - setSidebarCollapsed={setSidebarCollapsed} + setParentOpen={setOpen} /> ) diff --git a/components/docs/Sidebar/ListItems.jsx b/components/docs/Sidebar/ListItems.jsx index 3be5c0d5af..a07707a254 100644 --- a/components/docs/Sidebar/ListItems.jsx +++ b/components/docs/Sidebar/ListItems.jsx @@ -1,14 +1,14 @@ import SidebarLink from './SidebarLink' import Dropdown from './Dropdown' -export default function ListItems({ routes, setSidebarCollapsed }) { +export default function ListItems({ routes, setParentOpen }) { if (!routes) return null if (routes) { return routes.map((r, idx) => { if (!r.path) { return (
            • - +
            • ) } else { @@ -17,7 +17,7 @@ export default function ListItems({ routes, setSidebarCollapsed }) { ) diff --git a/components/docs/Sidebar/SidebarLink.jsx b/components/docs/Sidebar/SidebarLink.jsx index 0cb477faac..3420a55f27 100644 --- a/components/docs/Sidebar/SidebarLink.jsx +++ b/components/docs/Sidebar/SidebarLink.jsx @@ -1,3 +1,4 @@ +import React, { useEffect } from 'react'; import { useRouter } from 'next/router' import Link from 'next/link' import classNames from 'classnames' @@ -6,7 +7,7 @@ export default function SidebarLink({ href, caption, parentOpen = true, - setSidebarCollapsed + setParentOpen }) { const router = useRouter() const active = router.asPath === href + '/' @@ -14,12 +15,16 @@ export default function SidebarLink({ 'flex text-dark-2 hover:text-blue-2 text-base py-2 transition ease-in-out duration-150 no-underline': true, 'font-medium opacity-60 w-full': active }) + + useEffect(() => { + if (active) setParentOpen(true) + }, []); + return ( ( setSidebarCollapsed(true)}> + tabIndex={parentOpen ? 0 : -1}> {caption} diff --git a/components/docs/Sidebar/index.jsx b/components/docs/Sidebar/index.jsx index 52d18e8f88..a62d050fa2 100644 --- a/components/docs/Sidebar/index.jsx +++ b/components/docs/Sidebar/index.jsx @@ -40,7 +40,7 @@ export default function Sidebar({ router, routes, versions }) {
              @@ -51,7 +51,7 @@ export default function Sidebar({ router, routes, versions }) {
              diff --git a/components/docs/VersionSelect.jsx b/components/docs/VersionSelect.jsx index 255c4d1060..3ad3394441 100644 --- a/components/docs/VersionSelect.jsx +++ b/components/docs/VersionSelect.jsx @@ -13,7 +13,7 @@ function labelFromVersion(version) { export default function VersionSelect({ version, versions, - setSidebarCollapsed + setParentOpen }) { const [selectedVersion, setSelectedVersion] = useState(version) @@ -35,7 +35,7 @@ export default function VersionSelect({
              @@ -45,7 +45,7 @@ export default function VersionSelect({
              @@ -54,7 +54,7 @@ export default function VersionSelect({
              From 10ab547552f881c8bac19eff81aa79e4bea62a1d Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:20:14 +0200 Subject: [PATCH 176/264] fix broken approver-policy link Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- public/_redirects | 1 + 1 file changed, 1 insertion(+) diff --git a/public/_redirects b/public/_redirects index fa12757bca..2778abd2a5 100644 --- a/public/_redirects +++ b/public/_redirects @@ -196,6 +196,7 @@ https://docs.cert-manager.io/* https://cert-manager.io/docs/:splat 302! # Integrating the project pages into the main website /docs/usage/istio/ /docs/usage/istio-csr/ 301! +/docs/usage/approver-policy/ /docs/policy/approval/approver-policy/ 301! /docs/projects/istio-csr/ /docs/usage/istio-csr/ 301! /docs/projects/csi-driver/ /docs/usage/csi-driver/ 301! /docs/projects/csi-driver-spiffe/ /docs/usage/csi-driver-spiffe/ 301! From fdcd54778cbfebb046e1bf883b7e2b4c9f136d02 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:22:18 +0200 Subject: [PATCH 177/264] Partially revert "fix missing logic to unfold side menu when going to the link directly" Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- components/docs/Sidebar/Dropdown.jsx | 3 +++ components/docs/Sidebar/ListItems.jsx | 13 +++++++++++-- components/docs/Sidebar/SidebarLink.jsx | 4 +++- components/docs/Sidebar/index.jsx | 6 ++++-- components/docs/VersionSelect.jsx | 4 ++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/components/docs/Sidebar/Dropdown.jsx b/components/docs/Sidebar/Dropdown.jsx index 110ef317ed..d20299f5fe 100644 --- a/components/docs/Sidebar/Dropdown.jsx +++ b/components/docs/Sidebar/Dropdown.jsx @@ -6,6 +6,7 @@ import SidebarLink from './SidebarLink' export default function Dropdown({ routes, parentOpen = true, + setSidebarCollapsed, setParentOpen }) { const [open, setSelfOpen] = useState(false) @@ -45,6 +46,7 @@ export default function Dropdown({ routes={r} parentOpen={open} key={`${r.title}-${idx}`} + setSidebarCollapsed={setSidebarCollapsed} setParentOpen={setOpen} /> @@ -56,6 +58,7 @@ export default function Dropdown({ href={r.path} caption={r.title} parentOpen={open} + setSidebarCollapsed={setSidebarCollapsed} setParentOpen={setOpen} /> diff --git a/components/docs/Sidebar/ListItems.jsx b/components/docs/Sidebar/ListItems.jsx index a07707a254..652ed284ff 100644 --- a/components/docs/Sidebar/ListItems.jsx +++ b/components/docs/Sidebar/ListItems.jsx @@ -1,14 +1,22 @@ import SidebarLink from './SidebarLink' import Dropdown from './Dropdown' -export default function ListItems({ routes, setParentOpen }) { +export default function ListItems({ + routes, + setSidebarCollapsed, + setParentOpen +}) { if (!routes) return null if (routes) { return routes.map((r, idx) => { if (!r.path) { return (
            • - +
            • ) } else { @@ -17,6 +25,7 @@ export default function ListItems({ routes, setParentOpen }) { diff --git a/components/docs/Sidebar/SidebarLink.jsx b/components/docs/Sidebar/SidebarLink.jsx index 3420a55f27..9e27c0a183 100644 --- a/components/docs/Sidebar/SidebarLink.jsx +++ b/components/docs/Sidebar/SidebarLink.jsx @@ -7,6 +7,7 @@ export default function SidebarLink({ href, caption, parentOpen = true, + setSidebarCollapsed, setParentOpen }) { const router = useRouter() @@ -24,7 +25,8 @@ export default function SidebarLink({ ( + tabIndex={parentOpen ? 0 : -1} + onClick={() => setSidebarCollapsed(true)}> {caption} diff --git a/components/docs/Sidebar/index.jsx b/components/docs/Sidebar/index.jsx index a62d050fa2..ea9067fc0a 100644 --- a/components/docs/Sidebar/index.jsx +++ b/components/docs/Sidebar/index.jsx @@ -40,7 +40,8 @@ export default function Sidebar({ router, routes, versions }) {
                {}} />
              @@ -51,7 +52,8 @@ export default function Sidebar({ router, routes, versions }) { {}} /> diff --git a/components/docs/VersionSelect.jsx b/components/docs/VersionSelect.jsx index 3ad3394441..40738b7612 100644 --- a/components/docs/VersionSelect.jsx +++ b/components/docs/VersionSelect.jsx @@ -13,6 +13,7 @@ function labelFromVersion(version) { export default function VersionSelect({ version, versions, + setSidebarCollapsed, setParentOpen }) { const [selectedVersion, setSelectedVersion] = useState(version) @@ -35,6 +36,7 @@ export default function VersionSelect({ @@ -45,6 +47,7 @@ export default function VersionSelect({ @@ -54,6 +57,7 @@ export default function VersionSelect({ From 5e8369b10d4031f3af95f91819d65546391639b5 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:48:17 +0200 Subject: [PATCH 178/264] move copy icon to fix mobile view Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- styles/docs.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/docs.scss b/styles/docs.scss index e4bf11e36a..45a0132769 100644 --- a/styles/docs.scss +++ b/styles/docs.scss @@ -11,7 +11,7 @@ } .docs-codeblock-btn { - @apply text-blue-1 absolute top-3 -right-8; + @apply text-blue-1 absolute top-3 right-3; } .docs-tabs-mobile-select { From 44d0221072d3bc27d2c41f88bbd2fba0e8aa57a0 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:28:04 +0200 Subject: [PATCH 179/264] Update CLI flag documentation Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/cli/acmesolver.md | 1 + content/docs/cli/cainjector.md | 65 +++++++++++++++++++--------------- content/docs/cli/cmctl.md | 6 +++- content/docs/cli/controller.md | 41 +++++++++++---------- content/docs/cli/webhook.md | 52 ++++++++++++++------------- 5 files changed, 92 insertions(+), 73 deletions(-) diff --git a/content/docs/cli/acmesolver.md b/content/docs/cli/acmesolver.md index baee31aff4..799941ec2d 100644 --- a/content/docs/cli/acmesolver.md +++ b/content/docs/cli/acmesolver.md @@ -2,6 +2,7 @@ title: acmesolver CLI reference description: "cert-manager acmesolver CLI documentation" --- + ``` HTTP server used to solve ACME challenges. diff --git a/content/docs/cli/cainjector.md b/content/docs/cli/cainjector.md index 0bcdf50014..757eb98bde 100644 --- a/content/docs/cli/cainjector.md +++ b/content/docs/cli/cainjector.md @@ -2,8 +2,8 @@ title: cainjector CLI reference description: "cert-manager cainjector CLI documentation" --- -``` +``` cert-manager CA injector is a Kubernetes addon to automate the injection of CA data into webhooks and APIServices from cert-manager certificates. @@ -12,34 +12,41 @@ CA data from the referenced certificates, which can then be used to serve API servers and webhook servers. Usage: - ca-injector [flags] + cainjector [flags] Flags: - --add_dir_header If true, adds the file directory to the header of the log messages - --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) - --enable-profiling Enable profiling for cainjector - --feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are: - AllAlpha=true|false (ALPHA - default=false) - AllBeta=true|false (BETA - default=false) - -h, --help help for ca-injector - --kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster. - --leader-elect If true, cainjector will perform leader election between instances to ensure no more than one instance of cainjector operates at a time (default true) - --leader-election-lease-duration duration The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled. (default 1m0s) - --leader-election-namespace string Namespace used to perform leader election. Only used if leader election is enabled (default "kube-system") - --leader-election-renew-deadline duration The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled. (default 40s) - --leader-election-retry-period duration The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled. (default 15s) - --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) - --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) - --log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) - --log_file string If non-empty, use this log file (no effect when -logtostderr=true) - --log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) - --logtostderr log to standard error instead of files (default true) - --namespace string If set, this limits the scope of cainjector to a single namespace. If set, cainjector will not update resources with certificates outside of the configured namespace. - --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) - --profiler-address string Address of the Go profiler (pprof) if enabled. This should never be exposed on a public interface. (default "localhost:6060") - --skip_headers If true, avoid header prefixes in the log messages - --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) - --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) - -v, --v Level number for the log level verbosity - --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging + --add_dir_header If true, adds the file directory to the header of the log messages (DEPRECATED: this flag may be removed in the future) + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --enable-apiservices-injectable Inject CA data to annotated APIServices. This functionality is not required if cainjector is only used as cert-manager's internal component and setting it to false might reduce memory consumption (default true) + --enable-certificates-data-source Enable configuring cert-manager.io Certificate resources as potential sources for CA data. Requires cert-manager.io Certificate CRD to be installed. This data source can be disabled to reduce memory consumption if you only use cainjector as part of cert-manager's installation (default true) + --enable-customresourcedefinitions-injectable Inject CA data to annotated CustomResourceDefinitions. This functionality is not required if cainjecor is only used as cert-manager's internal component and setting it to false might slightly reduce memory consumption (default true) + --enable-mutatingwebhookconfigurations-injectable Inject CA data to annotated MutatingWebhookConfigurations. This functionality is required for cainjector to work correctly as cert-manager's internal component (default true) + --enable-profiling Enable profiling for controller. + --enable-validatingwebhookconfigurations-injectable Inject CA data to annotated ValidatingWebhookConfigurations. This functionality is required for cainjector to correctly function as cert-manager's internal component (default true) + --feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are: + AllAlpha=true|false (ALPHA - default=false) + AllBeta=true|false (BETA - default=false) + ServerSideApply=true|false (ALPHA - default=false) + -h, --help help for cainjector + --kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster. + --leader-elect If true, cainjector will perform leader election between instances to ensure no more than one instance of cainjector operates at a time + --leader-election-lease-duration duration The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled. + --leader-election-namespace string Namespace used to perform leader election. Only used if leader election is enabled + --leader-election-renew-deadline duration The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled. + --leader-election-retry-period duration The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled. + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) (DEPRECATED: this flag may be removed in the future) + --log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --log_file string If non-empty, use this log file (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) (DEPRECATED: this flag may be removed in the future) + --logging-format string Sets the log format. Permitted formats: "json" (gated by LoggingBetaOptions), "text". (default "text") + --logtostderr log to standard error instead of files (default true) (DEPRECATED: this flag may be removed in the future) + --namespace string If set, this limits the scope of cainjector to a single namespace. If set, cainjector will not update resources with certificates outside of the configured namespace. + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --profiler-address string The host and port that Go profiler should listen on, i.e localhost:6060. Ensure that profiler is not exposed on a public address. Profiler will be served at /debug/pprof. (default "localhost:6060") + --skip_headers If true, avoid header prefixes in the log messages (DEPRECATED: this flag may be removed in the future) + --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) (DEPRECATED: this flag may be removed in the future) + -v, --v Level number for the log level verbosity + --vmodule pattern=N,... comma-separated list of pattern=N settings for file-filtered logging (only works for text log format) ``` diff --git a/content/docs/cli/cmctl.md b/content/docs/cli/cmctl.md index dff83b3bc2..ca992f37ca 100644 --- a/content/docs/cli/cmctl.md +++ b/content/docs/cli/cmctl.md @@ -2,8 +2,8 @@ title: cmctl CLI reference description: "cert-manager cmctl CLI documentation" --- -``` +``` cmctl is a CLI tool manage and configure cert-manager resources for Kubernetes Usage: cmctl [command] @@ -11,6 +11,7 @@ Usage: cmctl [command] Available Commands: approve Approve a CertificateRequest check Check cert-manager components + completion Generate completion scripts for the cert-manager CLI convert Convert cert-manager config files between different API versions create Create cert-manager resources deny Deny a CertificateRequest @@ -25,6 +26,9 @@ Available Commands: Flags: -h, --help help for cmctl --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + --logging-format string Sets the log format. Permitted formats: "json" (gated by LoggingBetaOptions), "text". (default "text") + -v, --v Level[=2] number for the log level verbosity + --vmodule pattern=N,... comma-separated list of pattern=N settings for file-filtered logging (only works for text log format) Use "cmctl [command] --help" for more information about a command. ``` diff --git a/content/docs/cli/controller.md b/content/docs/cli/controller.md index 768ffb05cc..4121d6b093 100644 --- a/content/docs/cli/controller.md +++ b/content/docs/cli/controller.md @@ -2,8 +2,8 @@ title: controller CLI reference description: "cert-manager controller CLI documentation" --- -``` +``` cert-manager is a Kubernetes addon to automate the management and issuance of TLS certificates from various issuing sources. @@ -11,21 +11,23 @@ It will ensure certificates are valid and up to date periodically, and attempt to renew certificates at an appropriate time before expiry. Usage: - cert-manager-controller [flags] + controller [flags] Flags: - --acme-http01-solver-image string The docker image to use to solve ACME HTTP01 challenges. You most likely will not need to change this parameter unless you are testing a new feature or developing cert-manager. (default "quay.io/jetstack/cert-manager-acmesolver:canary") + --acme-http01-solver-image string The docker image to use to solve ACME HTTP01 challenges. You most likely will not need to change this parameter unless you are testing a new feature or developing cert-manager. (default "quay.io/jetstack/cert-manager-acmesolver:v1.13.1") --acme-http01-solver-nameservers strings A list of comma separated dns server endpoints used for ACME HTTP01 check requests. This should be a list containing host and port, for example 8.8.8.8:53,8.8.4.4:53 --acme-http01-solver-resource-limits-cpu string Defines the resource limits CPU size when spawning new ACME HTTP01 challenge solver pods. (default "100m") --acme-http01-solver-resource-limits-memory string Defines the resource limits Memory size when spawning new ACME HTTP01 challenge solver pods. (default "64Mi") --acme-http01-solver-resource-request-cpu string Defines the resource request CPU size when spawning new ACME HTTP01 challenge solver pods. (default "10m") --acme-http01-solver-resource-request-memory string Defines the resource request Memory size when spawning new ACME HTTP01 challenge solver pods. (default "64Mi") --acme-http01-solver-run-as-non-root Defines the ability to run the http01 solver as root for troubleshooting issues (default true) - --add_dir_header If true, adds the file directory to the header of the log messages - --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --add_dir_header If true, adds the file directory to the header of the log messages (DEPRECATED: this flag may be removed in the future) + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) --auto-certificate-annotations strings The annotation consumed by the ingress-shim controller to indicate a ingress is requesting a certificate (default [kubernetes.io/tls-acme]) --cluster-issuer-ambient-credentials Whether a cluster-issuer may make use of ambient credentials for issuers. 'Ambient Credentials' are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the ClusterIssuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata. (default true) --cluster-resource-namespace string Namespace to store resources owned by cluster scoped resources such as ClusterIssuer in. This must be specified if ClusterIssuers are enabled. (default "kube-system") + --concurrent-workers int The number of concurrent workers for each controller. (default 5) + --config string Path to a file containing a ControllerConfiguration object used to configure the controller --controllers strings A list of controllers to enable. '--controllers=*' enables all on-by-default controllers, '--controllers=foo' enables just the controller named 'foo', '--controllers=*,-foo' disables the controller named 'foo'. All controllers: issuers, clusterissuers, certificates-metrics, ingress-shim, gateway-shim, orders, challenges, certificaterequests-issuer-acme, certificaterequests-approver, certificaterequests-issuer-ca, certificaterequests-issuer-selfsigned, certificaterequests-issuer-vault, certificaterequests-issuer-venafi, certificates-trigger, certificates-issuing, certificates-key-manager, certificates-request-manager, certificates-readiness, certificates-revision-manager (default [*]) --copied-annotation-prefixes strings Specify which annotations should/shouldn't be copiedfrom Certificate to CertificateRequest and Order, as well as from CertificateSigningRequest to Order, by passing a list of annotation key prefixes.A prefix starting with a dash(-) specifies an annotation that shouldn't be copied. Example: '*,-kubectl.kuberenetes.io/'- all annotationswill be copied apart from the ones where the key is prefixed with 'kubectl.kubernetes.io/'. (default [*,-kubectl.kubernetes.io/,-fluxcd.io/,-argocd.argoproj.io/]) @@ -33,7 +35,7 @@ Flags: --default-issuer-kind string Kind of the Issuer to use when the tls is requested but issuer kind is not specified on the ingress resource. (default "Issuer") --default-issuer-name string Name of the Issuer to use when the tls is requested but issuer name is not specified on the ingress resource. --dns01-check-retry-period duration The duration the controller should wait between a propagation check. Despite the name, this flag is used to configure the wait period for both DNS01 and HTTP01 challenge propagation checks. For DNS01 challenges the propagation check verifies that a TXT record with the challenge token has been created. For HTTP01 challenges the propagation check verifies that the challenge token is served at the challenge URL.This should be a valid duration string, for example 180s or 1h (default 10s) - --dns01-recursive-nameservers strings A list of comma separated dns server endpoints used for DNS01 check requests. This should be a list containing host and port, for example 8.8.8.8:53,8.8.4.4:53 + --dns01-recursive-nameservers : A list of comma separated dns server endpoints used for DNS01 and DNS-over-HTTPS (DoH) check requests. This should be a list containing entries of the following formats: : or `https://`. For example: `8.8.8.8:53,8.8.4.4:53` or `https://1.1.1.1/dns-query,https://8.8.8.8/dns-query`. To make sure ALL DNS requests happen through DoH, `dns01-recursive-nameservers-only` should also be set to true. --dns01-recursive-nameservers-only When true, cert-manager will only ever query the configured DNS resolvers to perform the ACME DNS01 self check. This is useful in DNS constrained environments, where access to authoritative nameservers is restricted. Enabling this option could cause the DNS01 self check to take longer due to caching performed by the recursive nameservers. --enable-certificate-owner-ref Whether to set the certificate resource as an owner of secret where the tls certificate is stored. When this flag is enabled, the secret will be automatically removed when the certificate resource is deleted. --enable-profiling Enable profiling for controller. @@ -41,14 +43,16 @@ Flags: AdditionalCertificateOutputFormats=true|false (ALPHA - default=false) AllAlpha=true|false (ALPHA - default=false) AllBeta=true|false (BETA - default=false) + DisallowInsecureCSRUsageDefinition=true|false (BETA - default=true) ExperimentalCertificateSigningRequestControllers=true|false (ALPHA - default=false) ExperimentalGatewayAPISupport=true|false (ALPHA - default=false) LiteralCertificateSubject=true|false (ALPHA - default=false) + SecretsFilteredCaching=true|false (BETA - default=true) ServerSideApply=true|false (ALPHA - default=false) - StableCertificateRequestName=true|false (ALPHA - default=false) + StableCertificateRequestName=true|false (BETA - default=true) UseCertificateRequestBasicConstraints=true|false (ALPHA - default=false) ValidateCAA=true|false (ALPHA - default=false) - -h, --help help for cert-manager-controller + -h, --help help for controller --issuer-ambient-credentials Whether an issuer may make use of ambient credentials. 'Ambient Credentials' are credentials drawn from the environment, metadata services, or local files which are not explicitly configured in the Issuer API object. When this flag is enabled, the following sources for credentials are also used: AWS - All sources the Go SDK defaults to, notably including any EC2 IAM roles available via instance metadata. --kube-api-burst int the maximum burst queries-per-second of requests sent to the Kubernetes apiserver (default 50) --kube-api-qps float32 indicates the maximum queries-per-second requests to the Kubernetes apiserver (default 20) @@ -59,20 +63,21 @@ Flags: --leader-election-renew-deadline duration The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled. (default 40s) --leader-election-retry-period duration The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled. (default 15s) --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) - --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) - --log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) - --log_file string If non-empty, use this log file (no effect when -logtostderr=true) - --log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) - --logtostderr log to standard error instead of files (default true) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) (DEPRECATED: this flag may be removed in the future) + --log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --log_file string If non-empty, use this log file (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) (DEPRECATED: this flag may be removed in the future) + --logging-format string Sets the log format. Permitted formats: "json" (gated by LoggingBetaOptions), "text". (default "text") + --logtostderr log to standard error instead of files (default true) (DEPRECATED: this flag may be removed in the future) --master string Optional apiserver host address to connect to. If not specified, autoconfiguration will be attempted. --max-concurrent-challenges int The maximum number of challenges that can be scheduled as 'processing' at once. (default 60) --metrics-listen-address string The host and port that the metrics endpoint should listen on. (default "0.0.0.0:9402") --namespace string If set, this limits the scope of cert-manager to a single namespace and ClusterIssuers are disabled. If not specified, all namespaces will be watched - --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) --profiler-address string The host and port that Go profiler should listen on, i.e localhost:6060. Ensure that profiler is not exposed on a public address. Profiler will be served at /debug/pprof. (default "localhost:6060") - --skip_headers If true, avoid header prefixes in the log messages - --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) - --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + --skip_headers If true, avoid header prefixes in the log messages (DEPRECATED: this flag may be removed in the future) + --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) (DEPRECATED: this flag may be removed in the future) -v, --v Level number for the log level verbosity - --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging + --vmodule pattern=N,... comma-separated list of pattern=N settings for file-filtered logging (only works for text log format) ``` diff --git a/content/docs/cli/webhook.md b/content/docs/cli/webhook.md index f4d6ef30c3..8adb4add42 100644 --- a/content/docs/cli/webhook.md +++ b/content/docs/cli/webhook.md @@ -2,15 +2,20 @@ title: webhook CLI reference description: "cert-manager webhook CLI documentation" --- + ``` -Webhook component providing API validation, mutation and conversion functionality for cert-manager (canary) () +cert-manager is a Kubernetes addon to automate the management and issuance of +TLS certificates from various issuing sources. + +The webhook component provides API validation, mutation and conversion +functionality for cert-manager. Usage: webhook [flags] Flags: - --add-dir-header If true, adds the file directory to the header of the log messages - --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) + --add_dir_header If true, adds the file directory to the header of the log messages (DEPRECATED: this flag may be removed in the future) + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) --api-server-host string Optional apiserver host address to connect to. If not specified, autoconfiguration will be attempted. --config string Path to a file containing a WebhookConfiguration object used to configure the webhook --dynamic-serving-ca-secret-name string name of the secret used to store the CA that signs serving certificates certificates @@ -18,34 +23,31 @@ Flags: --dynamic-serving-dns-names strings DNS names that should be present on certificates generated by the dynamic serving CA --enable-profiling Enable profiling for webhook. --feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are: - AdditionalCertificateOutputFormats=true|false (ALPHA - default=false) - AllAlpha=true|false (ALPHA - default=false) - AllBeta=true|false (BETA - default=false) - ExperimentalCertificateSigningRequestControllers=true|false (ALPHA - default=false) - ExperimentalGatewayAPISupport=true|false (ALPHA - default=false) - LiteralCertificateSubject=true|false (ALPHA - default=false) - ServerSideApply=true|false (ALPHA - default=false) - StableCertificateRequestName=true|false (ALPHA - default=false) - UseCertificateRequestBasicConstraints=true|false (ALPHA - default=false) - ValidateCAA=true|false (ALPHA - default=false) - --healthz-port int port number to listen on for insecure healthz connections (default 6080) + AdditionalCertificateOutputFormats=true|false (ALPHA - default=false) + AllAlpha=true|false (ALPHA - default=false) + AllBeta=true|false (BETA - default=false) + DisallowInsecureCSRUsageDefinition=true|false (BETA - default=true) + LiteralCertificateSubject=true|false (ALPHA - default=false) + --healthz-port int32 port number to listen on for insecure healthz connections (default 6080) -h, --help help for webhook --kubeconfig string optional path to the kubeconfig used to connect to the apiserver. If not specified, in-cluster-config will be used - --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) - --log-dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) - --log-file string If non-empty, use this log file (no effect when -logtostderr=true) - --log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) - --logtostderr log to standard error instead of files (default true) - --one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) + --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) (DEPRECATED: this flag may be removed in the future) + --log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --log_file string If non-empty, use this log file (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) (DEPRECATED: this flag may be removed in the future) + --logging-format string Sets the log format. Permitted formats: "json" (gated by LoggingBetaOptions), "text". (default "text") + --logtostderr log to standard error instead of files (default true) (DEPRECATED: this flag may be removed in the future) + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) --profiler-address string Address of the Go profiler (pprof). This should never be exposed on a public interface. If this flag is not set, the profiler is not run. (default "localhost:6060") - --secure-port int port number to listen on for secure TLS connections (default 6443) - --skip-headers If true, avoid header prefixes in the log messages - --skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true) - --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) + --secure-port int32 port number to listen on for secure TLS connections (default 6443) + --skip_headers If true, avoid header prefixes in the log messages (DEPRECATED: this flag may be removed in the future) + --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) (DEPRECATED: this flag may be removed in the future) + --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) (DEPRECATED: this flag may be removed in the future) --tls-cert-file string path to the file containing the TLS certificate to serve with --tls-cipher-suites strings Comma-separated list of cipher suites for the server. If omitted, the default Go cipher suites will be use. Possible values: TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_RC4_128_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_RC4_128_SHA --tls-min-version string Minimum TLS version supported. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13 --tls-private-key-file string path to the file containing the TLS private key to serve with -v, --v Level number for the log level verbosity - --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging + --vmodule pattern=N,... comma-separated list of pattern=N settings for file-filtered logging (only works for text log format) ``` From d05cbf10bab45227e02cb933cd6b4d2ed80b1fd1 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:28:39 +0200 Subject: [PATCH 180/264] update support page: replace Jetstack with Venafi Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/pages/support.mdx | 10 +++++----- pages/support.jsx | 18 +++++++++--------- public/images/venafi-hero.png | Bin 0 -> 1103455 bytes .../venafi-tls-protect-for-kubernetes.svg | 13 +++++++++++++ 4 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 public/images/venafi-hero.png create mode 100644 public/images/venafi-tls-protect-for-kubernetes.svg diff --git a/content/pages/support.mdx b/content/pages/support.mdx index 3e65a73d4e..ea323da02a 100644 --- a/content/pages/support.mdx +++ b/content/pages/support.mdx @@ -6,15 +6,15 @@ export const meta = { description: 'Need a little more help?' }, intro: { - heading: 'Jetstack', + heading: 'Venafi', description: - 'Jetstack is the team who first created cert-manager and offer direct support alongside a commercial product offering to help enterprises and organisations scale effectively with cert-manager.', + 'Venafi is the principal maintainer of the cert-manager project and works directly with the CNCF. Commercial support for cert-manager is available as part of the TLS Protect for Kubernetes product which also offers expertise to help organisations scale effectively with cert-manager.', cta: { - logo: '/images/jetstack-secure-logo.svg', + logo: '/images/venafi-tls-protect-for-kubernetes.svg', description: - 'Extending the core value of cert-manager open source project.', + 'Extending the core value of the cert-manager open source project.', caption: 'Find out more', - href: 'https://www.jetstack.io/jetstack-secure/' + href: 'https://venafi.com/tls-protect-for-kubernetes/' } } } diff --git a/pages/support.jsx b/pages/support.jsx index c39a7b21ca..7fb0d00457 100644 --- a/pages/support.jsx +++ b/pages/support.jsx @@ -32,13 +32,13 @@ function Support({ router }) {

              {page.intro.heading}

              {page.intro.description}

              -
              +
              Jetstack secure @@ -47,16 +47,16 @@ function Support({ router }) { {page.intro.heading} -

              +

              {page.intro.cta.description}

              + className="inline-block bg-blue-1 px-6 py-3 rounded-5px text-white text-sm no-underline"> {page.intro.cta.caption} diff --git a/public/images/venafi-hero.png b/public/images/venafi-hero.png new file mode 100644 index 0000000000000000000000000000000000000000..b8b07cde960dfd01fb830466088059d4c3f83851 GIT binary patch literal 1103455 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
              8QF)AU_6)v&`o4rR!kQz?s@@^ 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

              `;ibiEzEiwh-q%c6 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�uy2bscas=4Q)#WuZ)7qDO-}paY8m5S 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∋*XNZS-6lFGeut&ld=D5SLGpv@gBX?%is| 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�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

              akBr96Tf|jrsHI%iHO!V;DHG+!rZA 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

              QCxZhL!SH? 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

              w*0V!1u`1&#Tfp2<`SC&2x|{dD8WHh2F6H|? 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OQM?G*`HFIPc@dr^ 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�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 literal 0 HcmV?d00001 diff --git a/public/images/venafi-tls-protect-for-kubernetes.svg b/public/images/venafi-tls-protect-for-kubernetes.svg new file mode 100644 index 0000000000..d57e61fa11 --- /dev/null +++ b/public/images/venafi-tls-protect-for-kubernetes.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + From 70f72c06f3fa509ba90da39e46ffeb56a31d4179 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:06:05 +0200 Subject: [PATCH 181/264] only include latest version of docs in sitemap Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- next-sitemap.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/next-sitemap.config.js b/next-sitemap.config.js index 4a9e068ab9..f7f2d8fbbf 100644 --- a/next-sitemap.config.js +++ b/next-sitemap.config.js @@ -4,7 +4,7 @@ module.exports = { changefreq: 'daily', priority: 0.7, sitemapSize: 7000, - exclude: ['[fallback]', '404', '/500', '*README'], + exclude: ['[fallback]', '404', '/500', '*README', '/v*-docs', '/v*-docs/*'], robotsTxtOptions: { policies: [ { From d34d97811cf3251d6da35d12f2fd88ba831a70bb Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:31:10 +0200 Subject: [PATCH 182/264] move release and upgrade notes to new "Releases" section Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 1 + .../docs/contributing/contributing-flow.md | 2 +- content/docs/contributing/release-process.md | 8 +- .../docs/installation/api-compatibility.md | 2 +- content/docs/installation/featureflags.md | 8 +- content/docs/installation/helm.md | 4 +- content/docs/installation/kubectl.md | 4 +- .../operator-lifecycle-manager.md | 2 +- .../{upgrading/README.md => upgrade.md} | 6 +- content/docs/manifest.json | 441 +++++++++--------- content/docs/reference/cmctl.md | 2 +- content/docs/release-notes/README.md | 35 -- .../README.md} | 42 +- .../release-notes/release-notes-0.1.md | 0 .../release-notes/release-notes-0.10.md | 0 .../release-notes/release-notes-0.11.md | 0 .../release-notes/release-notes-0.12.md | 0 .../release-notes/release-notes-0.13.md | 0 .../release-notes/release-notes-0.14.md | 4 +- .../release-notes/release-notes-0.15.md | 2 +- .../release-notes/release-notes-0.16.md | 8 +- .../release-notes/release-notes-0.2.md | 0 .../release-notes/release-notes-0.3.md | 0 .../release-notes/release-notes-0.4.md | 0 .../release-notes/release-notes-0.5.md | 0 .../release-notes/release-notes-0.6.md | 0 .../release-notes/release-notes-0.7.md | 0 .../release-notes/release-notes-0.8.md | 0 .../release-notes/release-notes-0.9.md | 0 .../release-notes/release-notes-1.0.md | 6 +- .../release-notes/release-notes-1.1.md | 4 +- .../release-notes/release-notes-1.10.md | 0 .../release-notes/release-notes-1.11.md | 8 +- .../release-notes/release-notes-1.12.md | 10 +- .../release-notes/release-notes-1.13.md | 0 .../release-notes/release-notes-1.2.md | 4 +- .../release-notes/release-notes-1.3.md | 2 +- .../release-notes/release-notes-1.4.md | 6 +- .../release-notes/release-notes-1.5.md | 4 +- .../release-notes/release-notes-1.6.md | 12 +- .../release-notes/release-notes-1.7.md | 8 +- .../release-notes/release-notes-1.8.md | 6 +- .../release-notes/release-notes-1.9.md | 0 .../upgrading/ingress-class-compatibility.md | 0 .../upgrading/remove-deprecated-apis.md | 0 .../upgrading/upgrading-0.10-0.11.md | 4 +- .../upgrading/upgrading-0.11-0.12.md | 2 +- .../upgrading/upgrading-0.12-0.13.md | 2 +- .../upgrading/upgrading-0.13-0.14.md | 2 +- .../upgrading/upgrading-0.14-0.15.md | 2 +- .../upgrading/upgrading-0.15-0.16.md | 2 +- .../upgrading/upgrading-0.16-1.0.md | 10 +- .../upgrading/upgrading-0.2-0.3.md | 0 .../upgrading/upgrading-0.3-0.4.md | 0 .../upgrading/upgrading-0.4-0.5.md | 0 .../upgrading/upgrading-0.5-0.6.md | 6 +- .../upgrading/upgrading-0.6-0.7.md | 0 .../upgrading/upgrading-0.7-0.8.md | 2 +- .../upgrading/upgrading-0.8-0.9.md | 2 +- .../upgrading/upgrading-0.9-0.10.md | 0 .../upgrading/upgrading-1.0-1.1.md | 2 +- .../upgrading/upgrading-1.1-1.2.md | 6 +- .../upgrading/upgrading-1.10-1.11.md | 2 +- .../upgrading/upgrading-1.11-1.12.md | 2 +- .../upgrading/upgrading-1.12-1.13.md | 2 +- .../upgrading/upgrading-1.2-1.3.md | 2 +- .../upgrading/upgrading-1.3-1.4.md | 4 +- .../upgrading/upgrading-1.4-1.5.md | 2 +- .../upgrading/upgrading-1.5-1.6.md | 2 +- .../upgrading/upgrading-1.6-1.7.md | 2 +- .../upgrading/upgrading-1.7-1.8.md | 2 +- .../upgrading/upgrading-1.8-1.9.md | 2 +- .../upgrading/upgrading-1.9-1.10.md | 4 +- public/_redirects | 5 + 74 files changed, 341 insertions(+), 373 deletions(-) rename content/docs/installation/{upgrading/README.md => upgrade.md} (96%) delete mode 100644 content/docs/release-notes/README.md rename content/docs/{installation/supported-releases.md => releases/README.md} (91%) rename content/docs/{ => releases}/release-notes/release-notes-0.1.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.10.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.11.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.12.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.13.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.14.md (98%) rename content/docs/{ => releases}/release-notes/release-notes-0.15.md (98%) rename content/docs/{ => releases}/release-notes/release-notes-0.16.md (89%) rename content/docs/{ => releases}/release-notes/release-notes-0.2.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.3.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.4.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.5.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.6.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.7.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.8.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-0.9.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-1.0.md (98%) rename content/docs/{ => releases}/release-notes/release-notes-1.1.md (94%) rename content/docs/{ => releases}/release-notes/release-notes-1.10.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-1.11.md (97%) rename content/docs/{ => releases}/release-notes/release-notes-1.12.md (98%) rename content/docs/{ => releases}/release-notes/release-notes-1.13.md (100%) rename content/docs/{ => releases}/release-notes/release-notes-1.2.md (96%) rename content/docs/{ => releases}/release-notes/release-notes-1.3.md (97%) rename content/docs/{ => releases}/release-notes/release-notes-1.4.md (98%) rename content/docs/{ => releases}/release-notes/release-notes-1.5.md (99%) rename content/docs/{ => releases}/release-notes/release-notes-1.6.md (96%) rename content/docs/{ => releases}/release-notes/release-notes-1.7.md (98%) rename content/docs/{ => releases}/release-notes/release-notes-1.8.md (98%) rename content/docs/{ => releases}/release-notes/release-notes-1.9.md (100%) rename content/docs/{installation => releases}/upgrading/ingress-class-compatibility.md (100%) rename content/docs/{installation => releases}/upgrading/remove-deprecated-apis.md (100%) rename content/docs/{installation => releases}/upgrading/upgrading-0.10-0.11.md (98%) rename content/docs/{installation => releases}/upgrading/upgrading-0.11-0.12.md (95%) rename content/docs/{installation => releases}/upgrading/upgrading-0.12-0.13.md (72%) rename content/docs/{installation => releases}/upgrading/upgrading-0.13-0.14.md (94%) rename content/docs/{installation => releases}/upgrading/upgrading-0.14-0.15.md (91%) rename content/docs/{installation => releases}/upgrading/upgrading-0.15-0.16.md (90%) rename content/docs/{installation => releases}/upgrading/upgrading-0.16-1.0.md (95%) rename content/docs/{installation => releases}/upgrading/upgrading-0.2-0.3.md (100%) rename content/docs/{installation => releases}/upgrading/upgrading-0.3-0.4.md (100%) rename content/docs/{installation => releases}/upgrading/upgrading-0.4-0.5.md (100%) rename content/docs/{installation => releases}/upgrading/upgrading-0.5-0.6.md (91%) rename content/docs/{installation => releases}/upgrading/upgrading-0.6-0.7.md (100%) rename content/docs/{installation => releases}/upgrading/upgrading-0.7-0.8.md (99%) rename content/docs/{installation => releases}/upgrading/upgrading-0.8-0.9.md (94%) rename content/docs/{installation => releases}/upgrading/upgrading-0.9-0.10.md (100%) rename content/docs/{installation => releases}/upgrading/upgrading-1.0-1.1.md (68%) rename content/docs/{installation => releases}/upgrading/upgrading-1.1-1.2.md (82%) rename content/docs/{installation => releases}/upgrading/upgrading-1.10-1.11.md (91%) rename content/docs/{installation => releases}/upgrading/upgrading-1.11-1.12.md (68%) rename content/docs/{installation => releases}/upgrading/upgrading-1.12-1.13.md (93%) rename content/docs/{installation => releases}/upgrading/upgrading-1.2-1.3.md (94%) rename content/docs/{installation => releases}/upgrading/upgrading-1.3-1.4.md (86%) rename content/docs/{installation => releases}/upgrading/upgrading-1.4-1.5.md (82%) rename content/docs/{installation => releases}/upgrading/upgrading-1.5-1.6.md (93%) rename content/docs/{installation => releases}/upgrading/upgrading-1.6-1.7.md (92%) rename content/docs/{installation => releases}/upgrading/upgrading-1.7-1.8.md (97%) rename content/docs/{installation => releases}/upgrading/upgrading-1.8-1.9.md (80%) rename content/docs/{installation => releases}/upgrading/upgrading-1.9-1.10.md (81%) diff --git a/.spelling b/.spelling index 6a2e900f0f..3bd4c0ab5a 100644 --- a/.spelling +++ b/.spelling @@ -666,6 +666,7 @@ README.md certificate.md pageinfo md#renew +upgrade.md # END TEMPORARY diff --git a/content/docs/contributing/contributing-flow.md b/content/docs/contributing/contributing-flow.md index bd5dff0669..3cd1e50b41 100644 --- a/content/docs/contributing/contributing-flow.md +++ b/content/docs/contributing/contributing-flow.md @@ -117,7 +117,7 @@ If this PR introduces a breaking change, the release note block must start with ### Cherry Picking If the pull request contains a critical bug fix then this should be cherry picked in to the current stable cert-manager branch -and [released as a patch release](../installation/supported-releases.md#support-policy). +and [released as a patch release](../releases#support-policy). To trigger the cherry-pick process, add a comment to the GitHub PR. For example: diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 30d273485a..3b00ebcae0 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -6,7 +6,7 @@ description: 'cert-manager contributing: Release process' This document aims to outline the process that should be followed for cutting a new release of cert-manager. If you would like to know more about current releases and the timeline for future releases, take a look at the -[Supported Releases](../installation/supported-releases.md) page. +[Supported Releases](../releases) page. ## Prerequisites @@ -189,7 +189,7 @@ page if a step is missing or if it is outdated. document is ready to be merged on [cert-manager/website](https://github.com/cert-manager/website). See for example, see - [upgrading-1.0-1.1](https://cert-manager.io/docs/installation/upgrading/upgrading-1.0-1.1/). + [upgrading-1.0-1.1](https://cert-manager.io/docs/releases/upgrading/upgrading-1.0-1.1/). 4. **(final + patch releases)** Prepare the Website "Release Notes" PR. @@ -246,10 +246,10 @@ page if a step is missing or if it is outdated. In that PR: 1. (**final release**) Update the section "Supported releases" in the - [supported-releases](../installation/supported-releases.md) page. + [supported-releases](../releases) page. 2. (**final release**) Update the section "How we determine supported Kubernetes versions" on the - [supported-releases](../installation/supported-releases.md) page. + [supported-releases](../releases) page. 3. (**final release**) Bump the version that appears in `scripts/gendocs/generate-new-import-path-docs`. For example: diff --git a/content/docs/installation/api-compatibility.md b/content/docs/installation/api-compatibility.md index e31b96ae29..e65567fcf7 100644 --- a/content/docs/installation/api-compatibility.md +++ b/content/docs/installation/api-compatibility.md @@ -19,4 +19,4 @@ As in upstream Kubernetes, We don't commit to preserving alpha or beta API versi In cert-manager v1.7 [all alpha and beta API versions prior to `v1` were removed](https://github.com/cert-manager/cert-manager/pull/4635). -NB: The Kubernetes deprecation policy notes that API removal introduces an issue with objects stored at the removed versions. To fix this, we wrote a [custom tool](https://cert-manager.io/docs/installation/upgrading/remove-deprecated-apis/) that users could run once to migrate their resources. +NB: The Kubernetes deprecation policy notes that API removal introduces an issue with objects stored at the removed versions. To fix this, we wrote a [custom tool](https://cert-manager.io/docs/releases/upgrading/remove-deprecated-apis/) that users could run once to migrate their resources. diff --git a/content/docs/installation/featureflags.md b/content/docs/installation/featureflags.md index e84ea50781..a3381d1f2a 100644 --- a/content/docs/installation/featureflags.md +++ b/content/docs/installation/featureflags.md @@ -47,18 +47,18 @@ Generally, we find user feedback most valuable when determining if a feature is See `--feature-gates` flags on cert-manager controller and webhook to enable any of these features. -- `AdditionalCertificateOutputFormats`. Added in cert-manager 1.7.0. Allows to specify additional formats in which cert-manager will store issued certificates and keys. See [release note](../release-notes/release-notes-1.7.md#additional-certificate-output-formats). Requires the feature to be enabled on both cert-manager controller and webhook +- `AdditionalCertificateOutputFormats`. Added in cert-manager 1.7.0. Allows to specify additional formats in which cert-manager will store issued certificates and keys. See [release note](../releases/release-notes/release-notes-1.7.md#additional-certificate-output-formats). Requires the feature to be enabled on both cert-manager controller and webhook - `ExperimentalCertificateSigningRequestControllers`. Added in cert-manager 1.4.0. Allows to use Kubernetes [CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) - resources with cert-manager. See [release notes](../release-notes/release-notes-1.4.md#experimental-support-for-kubernetes-certificatesigningrequests) + resources with cert-manager. See [release notes](../releases/release-notes/release-notes-1.4.md#experimental-support-for-kubernetes-certificatesigningrequests) - `ExperimentalGatewayAPISupport`. Added in cert-manager 1.5.0. Allows to use cert-manager to automatically issue certificates for `Gateway` resources as well as use `Gateway`s and `HTTPRoute`s to solve ACME HTTP-01 challenges. See [Securing Gateway resources](../usage/gateway.md) -- `LiteralCertificateSubject`. Added in cert-manager 1.9.0. Allows to specify certificate subject in a form that can be used to define a location in LDAP directory tree. See [release notes](../release-notes/release-notes-1.9.md#literal-certificate-subjects) +- `LiteralCertificateSubject`. Added in cert-manager 1.9.0. Allows to specify certificate subject in a form that can be used to define a location in LDAP directory tree. See [release notes](../releases/release-notes/release-notes-1.9.md#literal-certificate-subjects) -- `ServerSideApply`. Added in cert-manager 1.8.0. If this feature is enabled, cert-manager uses [Server side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) when creating or updating API resources. This will speed cert-manager operations and prevent the resource version conflict errors. See [release notes](../release-notes/release-notes-1.8.md#server-side-apply) +- `ServerSideApply`. Added in cert-manager 1.8.0. If this feature is enabled, cert-manager uses [Server side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) when creating or updating API resources. This will speed cert-manager operations and prevent the resource version conflict errors. See [release notes](../releases/release-notes/release-notes-1.8.md#server-side-apply) - `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.12.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index c979d63424..1df1bcd0bb 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -13,7 +13,7 @@ non-namespaced resources in your cluster and care must be taken to ensure that i ### Prerequisites - [Install Helm version 3 or later](https://helm.sh/docs/intro/install/). -- Install a [supported version of Kubernetes or OpenShift](./supported-releases.md). +- Install a [supported version of Kubernetes or OpenShift](../releases/). - Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. ### Steps @@ -59,7 +59,7 @@ must add the `--set installCRDs=true` flag to your Helm installation command. Uncomment the relevant line in the next steps to enable this. -Note that if you're using a `helm` version based on Kubernetes `v1.18` or below (Helm `v3.2`), `installCRDs` will not work with cert-manager `v0.16`. See the [v0.16 upgrade notes](./upgrading/upgrading-0.15-0.16.md#helm) for more details. +Note that if you're using a `helm` version based on Kubernetes `v1.18` or below (Helm `v3.2`), `installCRDs` will not work with cert-manager `v0.16`. See the [v0.16 upgrade notes](../releases/upgrading/upgrading-0.15-0.16.md#helm) for more details. #### 4. Install cert-manager diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index 117ccb884b..f5c4bfaaef 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -7,8 +7,8 @@ Learn how to install cert-manager using kubectl and static manifests. ## Prerequisites -- [Install `kubectl` version `>= v1.19.0`](https://kubernetes.io/docs/tasks/tools/). (otherwise, you'll have issues updating the CRDs - see [v0.16 upgrade notes](./upgrading/upgrading-0.15-0.16.md#issue-with-older-versions-of-kubectl)) -- Install a [supported version of Kubernetes or OpenShift](./supported-releases.md). +- [Install `kubectl` version `>= v1.19.0`](https://kubernetes.io/docs/tasks/tools/). (otherwise, you'll have issues updating the CRDs - see [v0.16 upgrade notes](../releases/upgrading/upgrading-0.15-0.16.md#issue-with-older-versions-of-kubectl)) +- Install a [supported version of Kubernetes or OpenShift](../releases/). - Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. ## Steps diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index b525c41edb..04328f4f72 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -7,7 +7,7 @@ description: 'cert-manager installation: Using OLM' ### Prerequisites -- Install a [supported version of Kubernetes or OpenShift](./supported-releases.md). +- Install a [supported version of Kubernetes or OpenShift](../releases/). - Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. ### Option 1: Installing from OperatorHub Web Console on OpenShift diff --git a/content/docs/installation/upgrading/README.md b/content/docs/installation/upgrade.md similarity index 96% rename from content/docs/installation/upgrading/README.md rename to content/docs/installation/upgrade.md index f29e0a7c5d..bff4e6cba6 100644 --- a/content/docs/installation/upgrading/README.md +++ b/content/docs/installation/upgrade.md @@ -10,7 +10,7 @@ versions, and information on things to look out for when upgrading. > Note: Before performing upgrades of cert-manager, it is advised to take a > backup of all your cert-manager resources just in case an issue occurs whilst > upgrading. You can read how to backup and restore cert-manager in the [backup -> and restore](../../devops-tips/backup.md) guide. +> and restore](../devops-tips/backup.md) guide. We recommend that you upgrade cert-manager one minor version at a time, always choosing the latest patch version for the minor version. You should always read @@ -87,7 +87,7 @@ number you want to install: kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download//cert-manager.yaml ``` -Once you have deployed the new version of cert-manager, you can [verify](../verify.md) the installation. +Once you have deployed the new version of cert-manager, you can [verify](verify.md) the installation. ## Reinstalling cert-manager @@ -118,4 +118,4 @@ cert-manager _including the CRDs_: - Is `--enable-certificate-owner-ref` flag currently set to true or could it have been set to true at some point previously? Due to an earlier bug, the owner reference that gets added to `Secret`s is _not_ removed when the value of `--enable-certificate-owner-ref` is changed from true to false, see [`cert-manager#4788`](https://github.com/cert-manager/cert-manager/issues/4788) - Are there currently any certificate issuances in progress? If so, with the custom resources deleted, the progress will be lost. This could potentially cause duplicated issuances. -- Is there a need to convert cert-manager custom resource manifests to v1 API? You can use [`cmctl convert` command](../../reference/cmctl.md#convert) to do that. +- Is there a need to convert cert-manager custom resource manifests to v1 API? You can use [`cmctl convert` command](../reference/cmctl.md#convert) to do that. diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 57c54f508f..d25c428093 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -13,176 +13,302 @@ "path": "/docs/getting-started/README.md" }, { - "title": "0. Installation", + "title": "Releases", "routes": [ - { - "title": "Introduction", - "path": "/docs/installation/README.md" - }, { "title": "Supported Releases", - "path": "/docs/installation/supported-releases.md" - }, - { - "title": "Cloud Compatibility", - "path": "/docs/installation/compatibility.md" - }, - { - "title": "a. kubectl apply", - "path": "/docs/installation/kubectl.md" + "path": "/docs/releases/README.md" }, { - "title": "b. Helm", - "path": "/docs/installation/helm.md" - }, - { - "title": "c. OperatorHub (OLM)", - "path": "/docs/installation/operator-lifecycle-manager.md" - }, - { - "title": "d. Other tools", - "path": "/docs/installation/other-tools.md" + "title": "v1.13", + "path": "/docs/releases/release-notes/release-notes-1.13.md" }, { - "title": "Feature flags", - "path": "/docs/installation/featureflags.md" + "title": "upgrade v1.12 to v1.13", + "path": "/docs/releases/upgrading/upgrading-1.12-1.13.md" }, { - "title": "Best Practice", - "path": "/docs/installation/best-practice.md" + "title": "v1.12", + "path": "/docs/releases/release-notes/release-notes-1.12.md" }, { - "title": "Verify installation", - "path": "/docs/installation/verify.md" + "title": "upgrade v1.11 to v1.12", + "path": "/docs/releases/upgrading/upgrading-1.11-1.12.md" }, { - "title": "Upgrading", + "title": "Older releases", "routes": [ - { - "title": "Introduction", - "path": "/docs/installation/upgrading/README.md" - }, { "title": "Notes on Ingress Class Compatibility", - "path": "/docs/installation/upgrading/ingress-class-compatibility.md" + "path": "/docs/releases/upgrading/ingress-class-compatibility.md" }, { "title": "Migrating Deprecated API Resources", - "path": "/docs/installation/upgrading/remove-deprecated-apis.md" + "path": "/docs/releases/upgrading/remove-deprecated-apis.md" + }, + { + "title": "v1.11", + "path": "/docs/releases/release-notes/release-notes-1.11.md" + }, + { + "title": "upgrade v1.10 to v1.11", + "path": "/docs/releases/upgrading/upgrading-1.10-1.11.md" + }, + { + "title": "v1.10", + "path": "/docs/releases/release-notes/release-notes-1.10.md" + }, + { + "title": "upgrade v1.9 to v1.10", + "path": "/docs/releases/upgrading/upgrading-1.9-1.10.md" + }, + { + "title": "v1.9", + "path": "/docs/releases/release-notes/release-notes-1.9.md" + }, + { + "title": "upgrade v1.8 to v1.9", + "path": "/docs/releases/upgrading/upgrading-1.8-1.9.md" + }, + { + "title": "v1.8", + "path": "/docs/releases/release-notes/release-notes-1.8.md" + }, + { + "title": "upgrade v1.7 to v1.8", + "path": "/docs/releases/upgrading/upgrading-1.7-1.8.md" }, { - "title": "v1.12 to v1.13", - "path": "/docs/installation/upgrading/upgrading-1.12-1.13.md" + "title": "v1.7", + "path": "/docs/releases/release-notes/release-notes-1.7.md" }, { - "title": "v1.11 to v1.12", - "path": "/docs/installation/upgrading/upgrading-1.11-1.12.md" + "title": "upgrade v1.6 to v1.7", + "path": "/docs/releases/upgrading/upgrading-1.6-1.7.md" }, { - "title": "v1.10 to v1.11", - "path": "/docs/installation/upgrading/upgrading-1.10-1.11.md" + "title": "v1.6", + "path": "/docs/releases/release-notes/release-notes-1.6.md" }, { - "title": "v1.9 to v1.10", - "path": "/docs/installation/upgrading/upgrading-1.9-1.10.md" + "title": "upgrade v1.5 to v1.6", + "path": "/docs/releases/upgrading/upgrading-1.5-1.6.md" }, { - "title": "v1.8 to v1.9", - "path": "/docs/installation/upgrading/upgrading-1.8-1.9.md" + "title": "v1.5", + "path": "/docs/releases/release-notes/release-notes-1.5.md" }, { - "title": "v1.7 to v1.8", - "path": "/docs/installation/upgrading/upgrading-1.7-1.8.md" + "title": "upgrade v1.4 to v1.5", + "path": "/docs/releases/upgrading/upgrading-1.4-1.5.md" }, { - "title": "v1.6 to v1.7", - "path": "/docs/installation/upgrading/upgrading-1.6-1.7.md" + "title": "v1.4", + "path": "/docs/releases/release-notes/release-notes-1.4.md" }, { - "title": "v1.5 to v1.6", - "path": "/docs/installation/upgrading/upgrading-1.5-1.6.md" + "title": "upgrade v1.3 to v1.4", + "path": "/docs/releases/upgrading/upgrading-1.3-1.4.md" }, { - "title": "v1.4 to v1.5", - "path": "/docs/installation/upgrading/upgrading-1.4-1.5.md" + "title": "v1.3", + "path": "/docs/releases/release-notes/release-notes-1.3.md" }, { - "title": "v1.3 to v1.4", - "path": "/docs/installation/upgrading/upgrading-1.3-1.4.md" + "title": "upgrade v1.2 to v1.3", + "path": "/docs/releases/upgrading/upgrading-1.2-1.3.md" }, { - "title": "v1.2 to v1.3", - "path": "/docs/installation/upgrading/upgrading-1.2-1.3.md" + "title": "v1.2", + "path": "/docs/releases/release-notes/release-notes-1.2.md" }, { - "title": "v1.1 to v1.2", - "path": "/docs/installation/upgrading/upgrading-1.1-1.2.md" + "title": "upgrade v1.1 to v1.2", + "path": "/docs/releases/upgrading/upgrading-1.1-1.2.md" }, { - "title": "v1.0 to v1.1", - "path": "/docs/installation/upgrading/upgrading-1.0-1.1.md" + "title": "v1.1", + "path": "/docs/releases/release-notes/release-notes-1.1.md" }, { - "title": "v0.16 to v1.0", - "path": "/docs/installation/upgrading/upgrading-0.16-1.0.md" + "title": "upgrade v1.0 to v1.1", + "path": "/docs/releases/upgrading/upgrading-1.0-1.1.md" }, { - "title": "v0.15 to v0.16", - "path": "/docs/installation/upgrading/upgrading-0.15-0.16.md" + "title": "v1.0", + "path": "/docs/releases/release-notes/release-notes-1.0.md" }, { - "title": "v0.14 to v0.15", - "path": "/docs/installation/upgrading/upgrading-0.14-0.15.md" + "title": "upgrade v0.16 to v1.0", + "path": "/docs/releases/upgrading/upgrading-0.16-1.0.md" }, { - "title": "v0.13 to v0.14", - "path": "/docs/installation/upgrading/upgrading-0.13-0.14.md" + "title": "v0.16", + "path": "/docs/releases/release-notes/release-notes-0.16.md" }, { - "title": "v0.12 to v0.13", - "path": "/docs/installation/upgrading/upgrading-0.12-0.13.md" + "title": "upgrade v0.15 to v0.16", + "path": "/docs/releases/upgrading/upgrading-0.15-0.16.md" }, { - "title": "v0.11 to v0.12", - "path": "/docs/installation/upgrading/upgrading-0.11-0.12.md" + "title": "v0.15", + "path": "/docs/releases/release-notes/release-notes-0.15.md" }, { - "title": "v0.10 to v0.11", - "path": "/docs/installation/upgrading/upgrading-0.10-0.11.md" + "title": "upgrade v0.14 to v0.15", + "path": "/docs/releases/upgrading/upgrading-0.14-0.15.md" }, { - "title": "v0.9 to v0.10", - "path": "/docs/installation/upgrading/upgrading-0.9-0.10.md" + "title": "v0.14", + "path": "/docs/releases/release-notes/release-notes-0.14.md" }, { - "title": "v0.8 to v0.9", - "path": "/docs/installation/upgrading/upgrading-0.8-0.9.md" + "title": "upgrade v0.13 to v0.14", + "path": "/docs/releases/upgrading/upgrading-0.13-0.14.md" }, { - "title": "v0.7 to v0.8", - "path": "/docs/installation/upgrading/upgrading-0.7-0.8.md" + "title": "v0.13", + "path": "/docs/releases/release-notes/release-notes-0.13.md" }, { - "title": "v0.6 to v0.7", - "path": "/docs/installation/upgrading/upgrading-0.6-0.7.md" + "title": "upgrade v0.12 to v0.13", + "path": "/docs/releases/upgrading/upgrading-0.12-0.13.md" }, { - "title": "v0.5 to v0.6", - "path": "/docs/installation/upgrading/upgrading-0.5-0.6.md" + "title": "v0.12", + "path": "/docs/releases/release-notes/release-notes-0.12.md" }, { - "title": "v0.4 to v0.5", - "path": "/docs/installation/upgrading/upgrading-0.4-0.5.md" + "title": "upgrade v0.11 to v0.12", + "path": "/docs/releases/upgrading/upgrading-0.11-0.12.md" }, { - "title": "v0.3 to v0.4", - "path": "/docs/installation/upgrading/upgrading-0.3-0.4.md" + "title": "v0.11", + "path": "/docs/releases/release-notes/release-notes-0.11.md" }, { - "title": "v0.2 to v0.3", - "path": "/docs/installation/upgrading/upgrading-0.2-0.3.md" + "title": "upgrade v0.10 to v0.11", + "path": "/docs/releases/upgrading/upgrading-0.10-0.11.md" + }, + { + "title": "v0.10", + "path": "/docs/releases/release-notes/release-notes-0.10.md" + }, + { + "title": "upgrade v0.9 to v0.10", + "path": "/docs/releases/upgrading/upgrading-0.9-0.10.md" + }, + { + "title": "v0.9", + "path": "/docs/releases/release-notes/release-notes-0.9.md" + }, + { + "title": "upgrade v0.8 to v0.9", + "path": "/docs/releases/upgrading/upgrading-0.8-0.9.md" + }, + { + "title": "v0.8", + "path": "/docs/releases/release-notes/release-notes-0.8.md" + }, + { + "title": "upgrade v0.7 to v0.8", + "path": "/docs/releases/upgrading/upgrading-0.7-0.8.md" + }, + { + "title": "v0.7", + "path": "/docs/releases/release-notes/release-notes-0.7.md" + }, + { + "title": "upgrade v0.6 to v0.7", + "path": "/docs/releases/upgrading/upgrading-0.6-0.7.md" + }, + { + "title": "v0.6", + "path": "/docs/releases/release-notes/release-notes-0.6.md" + }, + { + "title": "upgrade v0.5 to v0.6", + "path": "/docs/releases/upgrading/upgrading-0.5-0.6.md" + }, + { + "title": "v0.5", + "path": "/docs/releases/release-notes/release-notes-0.5.md" + }, + { + "title": "upgrade v0.4 to v0.5", + "path": "/docs/releases/upgrading/upgrading-0.4-0.5.md" + }, + { + "title": "v0.4", + "path": "/docs/releases/release-notes/release-notes-0.4.md" + }, + { + "title": "upgrade v0.3 to v0.4", + "path": "/docs/releases/upgrading/upgrading-0.3-0.4.md" + }, + { + "title": "v0.3", + "path": "/docs/releases/release-notes/release-notes-0.3.md" + }, + { + "title": "upgrade v0.2 to v0.3", + "path": "/docs/releases/upgrading/upgrading-0.2-0.3.md" + }, + { + "title": "v0.2", + "path": "/docs/releases/release-notes/release-notes-0.2.md" + }, + { + "title": "v0.1", + "path": "/docs/releases/release-notes/release-notes-0.1.md" } ] + } + ] + }, + + { + "title": "0. Installation", + "routes": [ + { + "title": "Introduction", + "path": "/docs/installation/README.md" + }, + { + "title": "Cloud Compatibility", + "path": "/docs/installation/compatibility.md" + }, + { + "title": "a. kubectl apply", + "path": "/docs/installation/kubectl.md" + }, + { + "title": "b. Helm", + "path": "/docs/installation/helm.md" + }, + { + "title": "c. OperatorHub (OLM)", + "path": "/docs/installation/operator-lifecycle-manager.md" + }, + { + "title": "d. Other tools", + "path": "/docs/installation/other-tools.md" + }, + { + "title": "Feature flags", + "path": "/docs/installation/featureflags.md" + }, + { + "title": "Best Practice", + "path": "/docs/installation/best-practice.md" + }, + { + "title": "Verify installation", + "path": "/docs/installation/verify.md" + }, + { + "title": "Upgrade", + "path": "/docs/installation/upgrade.md" }, { "title": "Uninstall", @@ -596,135 +722,6 @@ } ] }, - { - "title": "Release Notes", - "routes": [ - { - "title": "Introduction", - "path": "/docs/release-notes/README.md" - }, - { - "title": "v1.13", - "path": "/docs/release-notes/release-notes-1.13.md" - }, - { - "title": "v1.12", - "path": "/docs/release-notes/release-notes-1.12.md" - }, - { - "title": "v1.11", - "path": "/docs/release-notes/release-notes-1.11.md" - }, - { - "title": "v1.10", - "path": "/docs/release-notes/release-notes-1.10.md" - }, - { - "title": "v1.9", - "path": "/docs/release-notes/release-notes-1.9.md" - }, - { - "title": "v1.8", - "path": "/docs/release-notes/release-notes-1.8.md" - }, - { - "title": "v1.7", - "path": "/docs/release-notes/release-notes-1.7.md" - }, - { - "title": "v1.6", - "path": "/docs/release-notes/release-notes-1.6.md" - }, - { - "title": "v1.5", - "path": "/docs/release-notes/release-notes-1.5.md" - }, - { - "title": "v1.4", - "path": "/docs/release-notes/release-notes-1.4.md" - }, - { - "title": "v1.3", - "path": "/docs/release-notes/release-notes-1.3.md" - }, - { - "title": "v1.2", - "path": "/docs/release-notes/release-notes-1.2.md" - }, - { - "title": "v1.1", - "path": "/docs/release-notes/release-notes-1.1.md" - }, - { - "title": "v1.0", - "path": "/docs/release-notes/release-notes-1.0.md" - }, - { - "title": "v0.16", - "path": "/docs/release-notes/release-notes-0.16.md" - }, - { - "title": "v0.15", - "path": "/docs/release-notes/release-notes-0.15.md" - }, - { - "title": "v0.14", - "path": "/docs/release-notes/release-notes-0.14.md" - }, - { - "title": "v0.13", - "path": "/docs/release-notes/release-notes-0.13.md" - }, - { - "title": "v0.12", - "path": "/docs/release-notes/release-notes-0.12.md" - }, - { - "title": "v0.11", - "path": "/docs/release-notes/release-notes-0.11.md" - }, - { - "title": "v0.10", - "path": "/docs/release-notes/release-notes-0.10.md" - }, - { - "title": "v0.9", - "path": "/docs/release-notes/release-notes-0.9.md" - }, - { - "title": "v0.8", - "path": "/docs/release-notes/release-notes-0.8.md" - }, - { - "title": "v0.7", - "path": "/docs/release-notes/release-notes-0.7.md" - }, - { - "title": "v0.6", - "path": "/docs/release-notes/release-notes-0.6.md" - }, - { - "title": "v0.5", - "path": "/docs/release-notes/release-notes-0.5.md" - }, - { - "title": "v0.4", - "path": "/docs/release-notes/release-notes-0.4.md" - }, - { - "title": "v0.3", - "path": "/docs/release-notes/release-notes-0.3.md" - }, - { - "title": "v0.2", - "path": "/docs/release-notes/release-notes-0.2.md" - }, - { - "title": "v0.1", - "path": "/docs/release-notes/release-notes-0.1.md" - } - ] - }, { "title": "Reference", "routes": [ diff --git a/content/docs/reference/cmctl.md b/content/docs/reference/cmctl.md index 095c9dfe6e..0b3bfc957b 100644 --- a/content/docs/reference/cmctl.md +++ b/content/docs/reference/cmctl.md @@ -327,7 +327,7 @@ This command can be used to prepare a cert-manager installation that was created before cert-manager `v1` for upgrading to a cert-manager version `v1.6` or later. It ensures that any cert-manager custom resources that may have been stored in etcd at a deprecated API version get migrated to `v1`. See [Migrating Deprecated API -Resources](https://cert-manager.io/docs/installation/upgrading/remove-deprecated-apis) for more context. +Resources](https://cert-manager.io/docs/releases/upgrading/remove-deprecated-apis) for more context. ```bash $ cmctl upgrade migrate-api-version --qps 5 --burst 10 diff --git a/content/docs/release-notes/README.md b/content/docs/release-notes/README.md deleted file mode 100644 index 4a2a1551db..0000000000 --- a/content/docs/release-notes/README.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Release Notes -description: 'cert-manager release notes: Overview' ---- - -- [`v1.13`](./release-notes-1.13.md) -- [`v1.12`](./release-notes-1.12.md) -- [`v1.11`](./release-notes-1.11.md) -- [`v1.10`](./release-notes-1.10.md) -- [`v1.9`](./release-notes-1.9.md) -- [`v1.8`](./release-notes-1.8.md) -- [`v1.7`](./release-notes-1.7.md) -- [`v1.6`](./release-notes-1.6.md) -- [`v1.5`](./release-notes-1.5.md) -- [`v1.4`](./release-notes-1.4.md) -- [`v1.3`](./release-notes-1.3.md) -- [`v1.2`](./release-notes-1.2.md) -- [`v1.1`](./release-notes-1.1.md) -- [`v1.0`](./release-notes-1.0.md) -- [`v0.16`](./release-notes-0.16.md) -- [`v0.15`](./release-notes-0.15.md) -- [`v0.14`](./release-notes-0.14.md) -- [`v0.13`](./release-notes-0.13.md) -- [`v0.12`](./release-notes-0.12.md) -- [`v0.11`](./release-notes-0.11.md) -- [`v0.10`](./release-notes-0.10.md) -- [`v0.9`](./release-notes-0.9.md) -- [`v0.8`](./release-notes-0.8.md) -- [`v0.7`](./release-notes-0.7.md) -- [`v0.6`](./release-notes-0.6.md) -- [`v0.5`](./release-notes-0.5.md) -- [`v0.4`](./release-notes-0.4.md) -- [`v0.3`](./release-notes-0.3.md) -- [`v0.2`](./release-notes-0.2.md) -- [`v0.1`](./release-notes-0.1.md) diff --git a/content/docs/installation/supported-releases.md b/content/docs/releases/README.md similarity index 91% rename from content/docs/installation/supported-releases.md rename to content/docs/releases/README.md index 4626fbb37c..14c30af732 100644 --- a/content/docs/installation/supported-releases.md +++ b/content/docs/releases/README.md @@ -59,31 +59,31 @@ Dates in the future are uncertain and might change. [s]: #kubernetes-supported-versions [1.14]: https://github.com/cert-manager/cert-manager/milestone/35 -[1.13]: https://cert-manager.io/docs/release-notes/release-notes-1.13 -[1.12]: https://cert-manager.io/docs/release-notes/release-notes-1.12 -[1.11]: https://cert-manager.io/docs/release-notes/release-notes-1.11 -[1.10]: https://cert-manager.io/docs/release-notes/release-notes-1.10 -[1.9]: https://cert-manager.io/docs/release-notes/release-notes-1.9 -[1.8]: https://cert-manager.io/docs/release-notes/release-notes-1.8 -[1.7]: https://cert-manager.io/docs/release-notes/release-notes-1.7 -[1.6]: https://cert-manager.io/docs/release-notes/release-notes-1.6 -[1.5]: https://cert-manager.io/docs/release-notes/release-notes-1.5 -[1.4]: https://cert-manager.io/docs/release-notes/release-notes-1.4 -[1.3]: https://cert-manager.io/docs/release-notes/release-notes-1.3 -[1.2]: https://cert-manager.io/docs/release-notes/release-notes-1.2 -[1.1]: https://cert-manager.io/docs/release-notes/release-notes-1.1 -[1.0]: https://cert-manager.io/docs/release-notes/release-notes-1.0 -[0.16]: https://cert-manager.io/docs/release-notes/release-notes-0.16 -[0.15]: https://cert-manager.io/docs/release-notes/release-notes-0.15 -[0.14]: https://cert-manager.io/docs/release-notes/release-notes-0.14 -[0.13]: https://cert-manager.io/docs/release-notes/release-notes-0.13 -[0.12]: https://cert-manager.io/docs/release-notes/release-notes-0.12 -[0.11]: https://cert-manager.io/docs/release-notes/release-notes-0.11 +[1.13]: ./release-notes/release-notes-1.13.md +[1.12]: ./release-notes/release-notes-1.12.md +[1.11]: ./release-notes/release-notes-1.11.md +[1.10]: ./release-notes/release-notes-1.10.md +[1.9]: ./release-notes/release-notes-1.9.md +[1.8]: ./release-notes/release-notes-1.8.md +[1.7]: ./release-notes/release-notes-1.7.md +[1.6]: ./release-notes/release-notes-1.6.md +[1.5]: ./release-notes/release-notes-1.5.md +[1.4]: ./release-notes/release-notes-1.4.md +[1.3]: ./release-notes/release-notes-1.3.md +[1.2]: ./release-notes/release-notes-1.2.md +[1.1]: ./release-notes/release-notes-1.1.md +[1.0]: ./release-notes/release-notes-1.0.md +[0.16]: ./release-notes/release-notes-0.16.md +[0.15]: ./release-notes/release-notes-0.15.md +[0.14]: ./release-notes/release-notes-0.14.md +[0.13]: ./release-notes/release-notes-0.13.md +[0.12]: ./release-notes/release-notes-0.12.md +[0.11]: ./release-notes/release-notes-0.11.md We list cert-manager releases on [GitHub](https://github.com/cert-manager/cert-manager/releases), and release notes on [cert-manager.io](https://cert-manager.io/docs/release-notes/). -We also maintain detailed [upgrade instructions](https://cert-manager.io/docs/installation/upgrading/). +We also maintain detailed [upgrade instructions](https://cert-manager.io/docs/releases/upgrading/). ## Support policy diff --git a/content/docs/release-notes/release-notes-0.1.md b/content/docs/releases/release-notes/release-notes-0.1.md similarity index 100% rename from content/docs/release-notes/release-notes-0.1.md rename to content/docs/releases/release-notes/release-notes-0.1.md diff --git a/content/docs/release-notes/release-notes-0.10.md b/content/docs/releases/release-notes/release-notes-0.10.md similarity index 100% rename from content/docs/release-notes/release-notes-0.10.md rename to content/docs/releases/release-notes/release-notes-0.10.md diff --git a/content/docs/release-notes/release-notes-0.11.md b/content/docs/releases/release-notes/release-notes-0.11.md similarity index 100% rename from content/docs/release-notes/release-notes-0.11.md rename to content/docs/releases/release-notes/release-notes-0.11.md diff --git a/content/docs/release-notes/release-notes-0.12.md b/content/docs/releases/release-notes/release-notes-0.12.md similarity index 100% rename from content/docs/release-notes/release-notes-0.12.md rename to content/docs/releases/release-notes/release-notes-0.12.md diff --git a/content/docs/release-notes/release-notes-0.13.md b/content/docs/releases/release-notes/release-notes-0.13.md similarity index 100% rename from content/docs/release-notes/release-notes-0.13.md rename to content/docs/releases/release-notes/release-notes-0.13.md diff --git a/content/docs/release-notes/release-notes-0.14.md b/content/docs/releases/release-notes/release-notes-0.14.md similarity index 98% rename from content/docs/release-notes/release-notes-0.14.md rename to content/docs/releases/release-notes/release-notes-0.14.md index 7f6ccb6713..46f972d34b 100644 --- a/content/docs/release-notes/release-notes-0.14.md +++ b/content/docs/releases/release-notes/release-notes-0.14.md @@ -11,7 +11,7 @@ The `v0.14` release has a few focus areas: * Support for older Kubernetes and OpenShift versions * Experimental 'bundle' output format for Certificates -As usual, please read the [upgrade notes](../installation/upgrading/upgrading-0.13-0.14.md) before upgrading. +As usual, please read the [upgrade notes](../upgrading/upgrading-0.13-0.14.md) before upgrading. ## Webhook changes @@ -53,7 +53,7 @@ Thanks to this conversion webhook, this upgrade and future upgrades after it sho these kinds of changes to our API will enable the `v1beta1` API version to be released in a seamless manner in an upcoming release too. -More information on the webhook can be found in the [concepts section](../concepts/webhook.md). +More information on the webhook can be found in the [concepts section](../../concepts/webhook.md). ## Support for Kubernetes 1.11 and OpenShift 3.11 diff --git a/content/docs/release-notes/release-notes-0.15.md b/content/docs/releases/release-notes/release-notes-0.15.md similarity index 98% rename from content/docs/release-notes/release-notes-0.15.md rename to content/docs/releases/release-notes/release-notes-0.15.md index a91f110a8b..47ca5264f9 100644 --- a/content/docs/release-notes/release-notes-0.15.md +++ b/content/docs/releases/release-notes/release-notes-0.15.md @@ -12,7 +12,7 @@ The `v0.15` release has a few focus areas: * General Availability of JKS and PKCS#12 keystore support * kubectl cert-manager CLI plugin allowing manual renewal and API version conversion -As usual, please read the [upgrade notes](../installation/upgrading/upgrading-0.14-0.15.md) before upgrading. +As usual, please read the [upgrade notes](../upgrading/upgrading-0.14-0.15.md) before upgrading. ## Experimental controllers diff --git a/content/docs/release-notes/release-notes-0.16.md b/content/docs/releases/release-notes/release-notes-0.16.md similarity index 89% rename from content/docs/release-notes/release-notes-0.16.md rename to content/docs/releases/release-notes/release-notes-0.16.md index 40b98c2292..2862c52f4b 100644 --- a/content/docs/release-notes/release-notes-0.16.md +++ b/content/docs/releases/release-notes/release-notes-0.16.md @@ -10,7 +10,7 @@ The `v0.16` release has a few focus areas: * `v1beta1` API -As usual, please read the [upgrade notes](../installation/upgrading/upgrading-0.15-0.16.md) before upgrading. +As usual, please read the [upgrade notes](../upgrading/upgrading-0.15-0.16.md) before upgrading. ## New certificate controller @@ -40,12 +40,12 @@ private key and X.509 certificate in `my-cr.key` and `my-cr.crt` respectively. $ kubectl cert-manager create certificaterequest my-cr --from-certificate-file my-certificate.yaml --fetch-certificate --timeout 20m ``` -More information can be found on our [kubectl plugin page](../reference/cmctl.md#legacy-kubectl-plugin). +More information can be found on our [kubectl plugin page](../../reference/cmctl.md#legacy-kubectl-plugin). ## `v1beta1` API We are soon reaching cert-manager `v1.0` and the new `v1beta1` API is our first step towards a stable `v1` API. -The biggest change users may notice is the improved API documentation. We took the time to review and update all the user-facing APIs. You can view the [updated API documentation online](../reference/api-docs.md), or use `kubectl explain` after installing this version of cert-manager. +The biggest change users may notice is the improved API documentation. We took the time to review and update all the user-facing APIs. You can view the [updated API documentation online](../../reference/api-docs.md), or use `kubectl explain` after installing this version of cert-manager. `v1beta1` does not contain many big changes, this version is focused on streamlining field names and general clean up of the API in preparation for the release of the `v1` release. These are the changes made (for reference, our conversion will take care of everything for you): @@ -88,4 +88,4 @@ The `kubectl cert-manager convert` command will be able to convert your manifest $ kubectl cert-manager convert --output-version cert-manager.io/v1beta1 cert.yaml ``` -More information can be found on our [kubectl plugin page](../reference/cmctl.md#legacy-kubectl-plugin). +More information can be found on our [kubectl plugin page](../../reference/cmctl.md#legacy-kubectl-plugin). diff --git a/content/docs/release-notes/release-notes-0.2.md b/content/docs/releases/release-notes/release-notes-0.2.md similarity index 100% rename from content/docs/release-notes/release-notes-0.2.md rename to content/docs/releases/release-notes/release-notes-0.2.md diff --git a/content/docs/release-notes/release-notes-0.3.md b/content/docs/releases/release-notes/release-notes-0.3.md similarity index 100% rename from content/docs/release-notes/release-notes-0.3.md rename to content/docs/releases/release-notes/release-notes-0.3.md diff --git a/content/docs/release-notes/release-notes-0.4.md b/content/docs/releases/release-notes/release-notes-0.4.md similarity index 100% rename from content/docs/release-notes/release-notes-0.4.md rename to content/docs/releases/release-notes/release-notes-0.4.md diff --git a/content/docs/release-notes/release-notes-0.5.md b/content/docs/releases/release-notes/release-notes-0.5.md similarity index 100% rename from content/docs/release-notes/release-notes-0.5.md rename to content/docs/releases/release-notes/release-notes-0.5.md diff --git a/content/docs/release-notes/release-notes-0.6.md b/content/docs/releases/release-notes/release-notes-0.6.md similarity index 100% rename from content/docs/release-notes/release-notes-0.6.md rename to content/docs/releases/release-notes/release-notes-0.6.md diff --git a/content/docs/release-notes/release-notes-0.7.md b/content/docs/releases/release-notes/release-notes-0.7.md similarity index 100% rename from content/docs/release-notes/release-notes-0.7.md rename to content/docs/releases/release-notes/release-notes-0.7.md diff --git a/content/docs/release-notes/release-notes-0.8.md b/content/docs/releases/release-notes/release-notes-0.8.md similarity index 100% rename from content/docs/release-notes/release-notes-0.8.md rename to content/docs/releases/release-notes/release-notes-0.8.md diff --git a/content/docs/release-notes/release-notes-0.9.md b/content/docs/releases/release-notes/release-notes-0.9.md similarity index 100% rename from content/docs/release-notes/release-notes-0.9.md rename to content/docs/releases/release-notes/release-notes-0.9.md diff --git a/content/docs/release-notes/release-notes-1.0.md b/content/docs/releases/release-notes/release-notes-1.0.md similarity index 98% rename from content/docs/release-notes/release-notes-1.0.md rename to content/docs/releases/release-notes/release-notes-1.0.md index 94e67568f2..5101c235af 100644 --- a/content/docs/release-notes/release-notes-1.0.md +++ b/content/docs/releases/release-notes/release-notes-1.0.md @@ -27,7 +27,7 @@ The `v1.0` release is a stability release with a few focus areas: * ACME improvements -As usual, please read the [upgrade notes](../installation/upgrading/upgrading-0.16-1.0.md) before upgrading. +As usual, please read the [upgrade notes](../upgrading/upgrading-0.16-1.0.md) before upgrading. ## `v1` API @@ -48,7 +48,7 @@ This change makes these 2 SANs consistent with the other SANs as well as the Go If you're using Kubernetes 1.16 or higher, conversion webhooks will allow you seamlessly interact with `v1alpha2`, `v1alpha3`, `v1beta1` and `v1` API versions at the same time. This allows you to use the new API version without having to modify or redeploy your older resources. We highly recommend upgrading your manifests to the `v1` API as older versions will soon be deprecated. -Users of the `legacy` version of cert-manager will still only have the `v1` API, migration steps can be found in the [upgrade notes](../installation/upgrading/upgrading-0.16-1.0.md). +Users of the `legacy` version of cert-manager will still only have the `v1` API, migration steps can be found in the [upgrade notes](../upgrading/upgrading-0.16-1.0.md). ## `kubectl cert-manager status` command @@ -150,7 +150,7 @@ With this change we reduced the number of logs when you don't need to have a deb Tip: My default cert-manager runs on level 2 (Info), you can set this using `global.logLevel` in the Helm chart. -*Note*: Looking at the logs while troubleshooting cert-manager should be last resort behavior, for more info check out our [troubleshooting guide](../troubleshooting/README.md) +*Note*: Looking at the logs while troubleshooting cert-manager should be last resort behavior, for more info check out our [troubleshooting guide](../../troubleshooting/README.md) ## ACME improvements diff --git a/content/docs/release-notes/release-notes-1.1.md b/content/docs/releases/release-notes/release-notes-1.1.md similarity index 94% rename from content/docs/release-notes/release-notes-1.1.md rename to content/docs/releases/release-notes/release-notes-1.1.md index 082caaa095..d3ca28683b 100644 --- a/content/docs/release-notes/release-notes-1.1.md +++ b/content/docs/releases/release-notes/release-notes-1.1.md @@ -20,7 +20,7 @@ All help is very appreciated and very welcome! Interested in knowing what will happen in the next releases of cert-manager? Go check out [our road map](https://github.com/cert-manager/cert-manager/blob/master/ROADMAP.md)! -As usual, please read the [upgrade notes](../installation/upgrading/upgrading-1.0-1.1.md) before upgrading. +As usual, please read the [upgrade notes](../upgrading/upgrading-1.0-1.1.md) before upgrading. ## ACME Improvements @@ -48,5 +48,5 @@ This allows you to get more insight into any rate limiting or other errors your ## Improvements for Venafi TPP Authentication -It is now possible to use a long lived access-token for authentication when configuring [Venafi TPP `Issuer` and `ClusterIssuer` types](../configuration/venafi.md). +It is now possible to use a long lived access-token for authentication when configuring [Venafi TPP `Issuer` and `ClusterIssuer` types](../../configuration/venafi.md). This authentication mechanism is supported by `Venafi TPP >= 19.2`. \ No newline at end of file diff --git a/content/docs/release-notes/release-notes-1.10.md b/content/docs/releases/release-notes/release-notes-1.10.md similarity index 100% rename from content/docs/release-notes/release-notes-1.10.md rename to content/docs/releases/release-notes/release-notes-1.10.md diff --git a/content/docs/release-notes/release-notes-1.11.md b/content/docs/releases/release-notes/release-notes-1.11.md similarity index 97% rename from content/docs/release-notes/release-notes-1.11.md rename to content/docs/releases/release-notes/release-notes-1.11.md index b1e6a46a41..316fc75b7e 100644 --- a/content/docs/release-notes/release-notes-1.11.md +++ b/content/docs/releases/release-notes/release-notes-1.11.md @@ -63,9 +63,9 @@ Instead cert-manager authenticates to Azure using a short lived Kubernetes Servi This is now the recommended authentication method because it is more secure and easier to maintain than the other methods, and it should be used instead of the [deprecated pod-managed identify mechanism](https://github.com/Azure/aad-pod-identity#-announcement). -> 📖 Read about [configuring the ACME issuer with Azure DNS](../configuration/acme/dns01/azuredns.md). +> 📖 Read about [configuring the ACME issuer with Azure DNS](../../configuration/acme/dns01/azuredns.md). > -> 📖 Read the [AKS + LoadBalancer + Let's Encrypt tutorial](../tutorials/getting-started-aks-letsencrypt/README.md) for an end-to-end example of this authentication method. +> 📖 Read the [AKS + LoadBalancer + Let's Encrypt tutorial](../../tutorials/getting-started-aks-letsencrypt/README.md) for an end-to-end example of this authentication method. > > 🔗 See [pull request #5570](https://github.com/cert-manager/cert-manager/pull/5570) for the implementation @@ -76,7 +76,7 @@ complies with the ACME spec for the ACME issuer, but some users had issues when either that they ignore certificate validation (which is insecure) or that they hack their certificate into the cert-manager trust store. Now, users can set a caBundle flag on their ACME issuer, specifying the trust store that cert-manager should use when communicating with the -server. For more details, see [Private ACME Servers](../configuration/acme/README.md#private-acme-servers) +server. For more details, see [Private ACME Servers](../../configuration/acme/README.md#private-acme-servers) ### `LiteralSubject` Improvements @@ -96,7 +96,7 @@ Unless you've _explicitly_ opted in to using the Gateway API support, you don't anything. If you've been using the support, however, you might need to take some actions to ensure there aren't any breakages when you update. -Check out the [upgrade guide](../installation/upgrading/upgrading-1.10-1.11.md) for more +Check out the [upgrade guide](../upgrading/upgrading-1.10-1.11.md) for more details on what you'll need to do. ## Community diff --git a/content/docs/release-notes/release-notes-1.12.md b/content/docs/releases/release-notes/release-notes-1.12.md similarity index 98% rename from content/docs/release-notes/release-notes-1.12.md rename to content/docs/releases/release-notes/release-notes-1.12.md index ec906dd03d..97dddca56c 100644 --- a/content/docs/release-notes/release-notes-1.12.md +++ b/content/docs/releases/release-notes/release-notes-1.12.md @@ -140,7 +140,7 @@ See [`cert-manager#5976`](https://github.com/cert-manager/cert-manager/pull/5976 ##### Cainjector -[Cainjector's](../concepts/ca-injector.md) control loops have been refactored, so by default it should +[Cainjector's](../../concepts/ca-injector.md) control loops have been refactored, so by default it should consume up to half as much memory as before, see [`cert-manager#5746`](https://github.com/cert-manager/cert-manager/pull/5746). @@ -186,7 +186,7 @@ your `go.mod` files or potential version conflicts between cert-manager and your other dependencies. The caveat here is that we still only recommend importing cert-manager in [very -specific circumstances](../contributing/importing.md), and the module changes +specific circumstances](../../contributing/importing.md), and the module changes mean that if you imported some paths (specifically under `cmd` or some paths under `test`) you might see broken imports when you try to upgrade. @@ -208,7 +208,7 @@ cert-manager in a secretless manner. With this new feature, cert-manager will create an ephemeral service account token on your behalf and use that to authenticate to Vault. -> 📖 Read about [Secretless Authentication with a Service Account](../configuration/vault.md#secretless-authentication-with-a-service-account). +> 📖 Read about [Secretless Authentication with a Service Account](../../configuration/vault.md#secretless-authentication-with-a-service-account). This change was implemented in the pull request [`cert-manager#5502`](https://github.com/cert-manager/cert-manager/pull/5502). @@ -219,7 +219,7 @@ cert-manager now supports the `ingressClassName` field in the HTTP-01 solver. We recommend using `ingressClassName` instead of the field `class` in your Issuers and ClusterIssuers. -> 📖 Read more about `ingressClassName` in the documentation page [HTTP01](../configuration/acme/http01/#ingressclassname). +> 📖 Read more about `ingressClassName` in the documentation page [HTTP01](../../configuration/acme/http01/#ingressclassname). #### Liveness probe and healthz endpoint in the controller @@ -230,7 +230,7 @@ the `/livez` endpoint will return an error code and an error message. In conjunction with a new liveness probe in the controller Pod, this will cause the controller to be restarted by the kubelet. -> 📖 Read more about this new feature in [Best Practice: Use Liveness Probes](../installation/best-practice.md#use-liveness-probes). +> 📖 Read more about this new feature in [Best Practice: Use Liveness Probes](../../installation/best-practice.md#use-liveness-probes). ### Community diff --git a/content/docs/release-notes/release-notes-1.13.md b/content/docs/releases/release-notes/release-notes-1.13.md similarity index 100% rename from content/docs/release-notes/release-notes-1.13.md rename to content/docs/releases/release-notes/release-notes-1.13.md diff --git a/content/docs/release-notes/release-notes-1.2.md b/content/docs/releases/release-notes/release-notes-1.2.md similarity index 96% rename from content/docs/release-notes/release-notes-1.2.md rename to content/docs/releases/release-notes/release-notes-1.2.md index eb729ae383..1823eb932e 100644 --- a/content/docs/release-notes/release-notes-1.2.md +++ b/content/docs/releases/release-notes/release-notes-1.2.md @@ -7,7 +7,7 @@ description: 'cert-manager release notes: cert-manager v1.2' This release adds new features for several issuers and fixes several bugs. -Please read the [upgrade notes](../installation/upgrading/upgrading-1.1-1.2.md) before upgrading. +Please read the [upgrade notes](../upgrading/upgrading-1.1-1.2.md) before upgrading. Aside from that, there have been numerous bug fixes and features summarized below. @@ -15,7 +15,7 @@ Aside from that, there have been numerous bug fixes and features summarized belo 1. The `--renew-before-expiration-duration` flag of the cert-manager controller-manager has been deprecated. Please set the `Certificate.Spec.RenewBefore` field instead. This flag will be removed in the next release. -2. As Kubernetes `v1.16` is now the earliest supported version, The `legacy` manifests have now been removed. You can read more [here](../installation/supported-releases.md). +2. As Kubernetes `v1.16` is now the earliest supported version, The `legacy` manifests have now been removed. You can read more [here](../). 3. The `User-Agent` request header has been changed from `jetstack-cert-manager/` to `cert-manager/`. This may affect functionality if you rely on an a User-Agent allowlist in a corporate environment. diff --git a/content/docs/release-notes/release-notes-1.3.md b/content/docs/releases/release-notes/release-notes-1.3.md similarity index 97% rename from content/docs/release-notes/release-notes-1.3.md rename to content/docs/releases/release-notes/release-notes-1.3.md index b9bc9641b2..4f7a0a5f31 100644 --- a/content/docs/release-notes/release-notes-1.3.md +++ b/content/docs/releases/release-notes/release-notes-1.3.md @@ -35,7 +35,7 @@ Special thanks to the external contributors who contributed to this release: * [@OmairK](https://github.com/OmairK) * [@justinkillen](https://github.com/justinkillen) -Please read the [upgrade notes](../installation/upgrading/upgrading-1.2-1.3.md) before upgrading. +Please read the [upgrade notes](../upgrading/upgrading-1.2-1.3.md) before upgrading. As always, the full change log is available on the [GitHub release](https://github.com/cert-manager/cert-manager/releases/tag/v1.3.0). diff --git a/content/docs/release-notes/release-notes-1.4.md b/content/docs/releases/release-notes/release-notes-1.4.md similarity index 98% rename from content/docs/release-notes/release-notes-1.4.md rename to content/docs/releases/release-notes/release-notes-1.4.md index 4620620ae4..a73c1310c4 100644 --- a/content/docs/release-notes/release-notes-1.4.md +++ b/content/docs/releases/release-notes/release-notes-1.4.md @@ -42,7 +42,7 @@ and which is therefore available on Please uninstall the existing cert-manager package and re-install by following the [OLM Installation Documentation][]. -[OLM Installation Documentation]: ../installation/operator-lifecycle-manager.md +[OLM Installation Documentation]: ../../installation/operator-lifecycle-manager.md ### Upgrading cert-manager CRDs and stored versions of cert-manager custom resources @@ -65,7 +65,7 @@ resources are stored in `etcd` at `v1` version and that cert-manager CRDs do not reference the deprecated APIs **by the time you upgrade to `v1.6`**. This is explained in more detail in the [Upgrading existing cert-manager -resources](../installation/upgrading/remove-deprecated-apis.md#upgrading-existing-cert-manager-resources) +resources](../upgrading/remove-deprecated-apis.md#upgrading-existing-cert-manager-resources) page.

              @@ -168,7 +168,7 @@ Note that you will still need to manually approve the CSR object before cert-manager can sign the CSR. The documentation is available on the [the Kubernetes CSR usage -page](../usage/kube-csr.md). +page](../../usage/kube-csr.md). Implemented in cert-manager PR [#4064][]. diff --git a/content/docs/release-notes/release-notes-1.5.md b/content/docs/releases/release-notes/release-notes-1.5.md similarity index 99% rename from content/docs/release-notes/release-notes-1.5.md rename to content/docs/releases/release-notes/release-notes-1.5.md index 8e585335e8..d3e03aa619 100644 --- a/content/docs/release-notes/release-notes-1.5.md +++ b/content/docs/releases/release-notes/release-notes-1.5.md @@ -36,7 +36,7 @@ Most people won't have any trouble upgrading from a version that contains the re If you are using Traefik, Istio, Ambassador, or ingress-nginx _and_ you are using a non-default value for the class (e.g., `istio-internal`), or if you experience any issues with your HTTP-01 challenges please read the [notes on Ingress v1 compatibility]. -[notes on Ingress v1 compatibility]: https://cert-manager.io/docs/installation/upgrading/ingress-class-compatibility/ +[notes on Ingress v1 compatibility]: https://cert-manager.io/docs/releases/upgrading/ingress-class-compatibility/ #### Bug or Regression @@ -110,7 +110,7 @@ cert-manager 1.5 is now compatible with both Ingress `v1` and `v1beta1`. cert-manager will default to using `v1` Ingress, and fall back to `v1beta1` when `v1` is not available. -Please read the [Ingress class compatibility](../installation/upgrading/ingress-class-compatibility.md) +Please read the [Ingress class compatibility](../upgrading/ingress-class-compatibility.md) notes to see if your Ingress controller has any known issues. Additionally, the cert-manager API versions `v1alpha2`, `v1alpha3` and `v1beta1` diff --git a/content/docs/release-notes/release-notes-1.6.md b/content/docs/releases/release-notes/release-notes-1.6.md similarity index 96% rename from content/docs/release-notes/release-notes-1.6.md rename to content/docs/releases/release-notes/release-notes-1.6.md index 26d4460efa..74a9ce2809 100644 --- a/content/docs/release-notes/release-notes-1.6.md +++ b/content/docs/releases/release-notes/release-notes-1.6.md @@ -43,7 +43,7 @@ Most people won't have any trouble upgrading from 1.6.0 or 1.6.1 to 1.6.2. If yo If you are using Traefik, Istio, Ambassador, or ingress-nginx _and_ you are using a non-default value for the class (e.g., `istio-internal`), or if you experience any issues with your HTTP-01 challenges please read the [notes on Ingress v1 compatibility]. -[notes on Ingress v1 compatibility]: https://cert-manager.io/docs/installation/upgrading/ingress-class-compatibility/ +[notes on Ingress v1 compatibility]: https://cert-manager.io/docs/releases/upgrading/ingress-class-compatibility/ ### Changelog since v1.6.1 @@ -73,7 +73,7 @@ If you are using Traefik, Istio, Ambassador, or ingress-nginx _and_ you are usin Following their deprecation in version 1.4, the cert-manager API versions `v1alpha2, v1alpha3, and v1beta1` are no longer served. -This means if your deployment manifests contain any of these API versions, you will not be able to deploy them after upgrading. Our new `cmctl` utility or old `kubectl cert-manager` plugin can [convert](../reference/cmctl.md#convert) old manifests to `v1` for you. +This means if your deployment manifests contain any of these API versions, you will not be able to deploy them after upgrading. Our new `cmctl` utility or old `kubectl cert-manager` plugin can [convert](../../reference/cmctl.md#convert) old manifests to `v1` for you.
              @@ -82,7 +82,7 @@ cert-manager < `v1.0.0`, you will need to ensure that all cert-manager custom resources are stored in `etcd` at `v1` version and that cert-manager CRDs do not reference the deprecated APIs **before you upgrade to `v1.6`**. -This is explained in more detail in the [Upgrading existing cert-manager resources](../installation/upgrading/remove-deprecated-apis.md#upgrading-existing-cert-manager-resources) +This is explained in more detail in the [Upgrading existing cert-manager resources](../upgrading/remove-deprecated-apis.md#upgrading-existing-cert-manager-resources) page.
              @@ -99,7 +99,7 @@ If you are using a shorter password, certificates would have failed to renew, and the only observable error was in the cert-manager logs. This was fixed in cert-manager `v1.6.1`. -[jks-keystore]: ../reference/api-docs.md#cert-manager.io/v1.CertificateKeystores +[jks-keystore]: ../../reference/api-docs.md#cert-manager.io/v1.CertificateKeystores [jks-keystore-upgrade-pr]: https://github.com/cert-manager/cert-manager/pull/4428 ### Major Themes @@ -110,11 +110,11 @@ The cert-manager kubectl plugin has been redesigned as a [standalone utility: `c While the kubectl plugin functionality remains intact, using `cmctl` allows for full tab completion. -[cmctl]: ../reference/cmctl.md +[cmctl]: ../../reference/cmctl.md #### Supply Chain Security -As part of the wider ecosystem's push for greater supply chain security we are aiming to achieve [SLSA 3](https://slsa.dev/levels#level-requirements) by the 1.7 release date. cert-manager 1.6 has achieved the requirements for SLSA 2 when installed via helm. Our helm chart's signature can be verified with the cert-manager maintainers' public key [published on our website](../installation/code-signing.md). +As part of the wider ecosystem's push for greater supply chain security we are aiming to achieve [SLSA 3](https://slsa.dev/levels#level-requirements) by the 1.7 release date. cert-manager 1.6 has achieved the requirements for SLSA 2 when installed via helm. Our helm chart's signature can be verified with the cert-manager maintainers' public key [published on our website](../../installation/code-signing.md). Our container images will be signed using sigstore's [cosign](https://github.com/sigstore/cosign) as soon as our OCI registry supports it. diff --git a/content/docs/release-notes/release-notes-1.7.md b/content/docs/releases/release-notes/release-notes-1.7.md similarity index 98% rename from content/docs/release-notes/release-notes-1.7.md rename to content/docs/releases/release-notes/release-notes-1.7.md index 06853b8157..8727e00d09 100644 --- a/content/docs/release-notes/release-notes-1.7.md +++ b/content/docs/releases/release-notes/release-notes-1.7.md @@ -54,7 +54,7 @@ Please [download `cmctl-v1.7.1`] and read [Migrating Deprecated API Resources] for full instructions. [download `cmctl-v1.7.1`]: https://github.com/cert-manager/cert-manager/releases/tag/v1.7.1 -[Migrating Deprecated API Resources]: https://cert-manager.io/docs/installation/upgrading/remove-deprecated-apis/ +[Migrating Deprecated API Resources]: https://cert-manager.io/docs/releases/upgrading/remove-deprecated-apis/ #### Ingress Class Semantics @@ -87,7 +87,7 @@ Most people won't have any trouble upgrading from a version that contains the re If you are using Traefik, Istio, Ambassador, or ingress-nginx _and_ you are using a non-default value for the class (e.g., `istio-internal`), or if you experience any issues with your HTTP-01 challenges please read the [notes on Ingress v1 compatibility]. -[notes on Ingress v1 compatibility]: https://cert-manager.io/docs/installation/upgrading/ingress-class-compatibility/ +[notes on Ingress v1 compatibility]: https://cert-manager.io/docs/releases/upgrading/ingress-class-compatibility/ #### Upgrading with Server Side Apply @@ -128,7 +128,7 @@ can be requested for the same certificate. Read [Additional Certificate Output Formats] for more details and thanks to [@seuf](https://github.com/seuf) for getting this across the line! -[Additional Certificate Output Formats]: ../usage/certificate.md#additional-certificate-output-formats +[Additional Certificate Output Formats]: ../../usage/certificate.md#additional-certificate-output-formats #### Server-Side Apply @@ -178,7 +178,7 @@ Thanks again to all open-source contributors with commits in this release, inclu And thanks as usual to [coderanger](https://github.com/coderanger) for helping people out on the [`#cert-manager` Slack channel]; it's a huge help and much appreciated. -[`#cert-manager` Slack channel]: ../contributing/README.md#slack +[`#cert-manager` Slack channel]: ../../contributing/README.md#slack ### Changelog since v1.6.0 diff --git a/content/docs/release-notes/release-notes-1.8.md b/content/docs/releases/release-notes/release-notes-1.8.md similarity index 98% rename from content/docs/release-notes/release-notes-1.8.md rename to content/docs/releases/release-notes/release-notes-1.8.md index 22221af56e..e5cc204636 100644 --- a/content/docs/release-notes/release-notes-1.8.md +++ b/content/docs/releases/release-notes/release-notes-1.8.md @@ -47,7 +47,7 @@ Version 1.8 also marks our first release in which the Go import path for cert-ma #### Validation of the `rotationPolicy` field -The field `spec.privateKey.rotationPolicy` on Certificate resources is now validated. Valid options are Never and Always. If you are using a GitOps flow and one of your YAML manifests contains a Certificate with an invalid value, you will need to update it with a valid value to prevent your GitOps tool from failing on the new validation. Please follow the instructions listed on the page [Upgrading from v1.7 to v1.8](https://cert-manager.io/docs/installation/upgrading/upgrading-1.7-1.8/). ([#4913](https://github.com/cert-manager/cert-manager/pull/4913), [@jahrlin](https://github.com/jahrlin)) +The field `spec.privateKey.rotationPolicy` on Certificate resources is now validated. Valid options are Never and Always. If you are using a GitOps flow and one of your YAML manifests contains a Certificate with an invalid value, you will need to update it with a valid value to prevent your GitOps tool from failing on the new validation. Please follow the instructions listed on the page [Upgrading from v1.7 to v1.8](https://cert-manager.io/docs/releases/upgrading/upgrading-1.7-1.8/). ([#4913](https://github.com/cert-manager/cert-manager/pull/4913), [@jahrlin](https://github.com/jahrlin)) ##### What happens if I upgrade to 1.8.0 without doing the above steps? @@ -123,7 +123,7 @@ These conflicts aren't usually actually a problem which will block the issuance reconcile loops. Server-side apply cleans things up, which should mean less noise in logs and fewer pointless reconcile loops. If you want to test it out, you can enable alpha-level cert-manager Server-Side Apply support through the -`--feature-gates` [controller flag](../cli/controller.md). +`--feature-gates` [controller flag](../../cli/controller.md). #### From Bazel to Make @@ -149,7 +149,7 @@ Previously, a failed issuance was retried every hour which — especially in lar are now retried with a binary exponential backoff starting with `1h` then `2h`, `4h` up to a maximum of `32h`. As part of the new backoff behavior, a new `failedIssuanceAttempts` field was added to the `Certificate` spec to track the number of currently failed issuances. -The `cmctl renew` [command](../cli/cmctl.md) command can still be used to force `Certificate` renewal immediately. +The `cmctl renew` [command](../../cli/cmctl.md) command can still be used to force `Certificate` renewal immediately. We're also considering reducing the initial backoff from 1 hour. If you have a use case where this would be useful please do comment on [our tracking issue](https://github.com/cert-manager/cert-manager/issues/4786). diff --git a/content/docs/release-notes/release-notes-1.9.md b/content/docs/releases/release-notes/release-notes-1.9.md similarity index 100% rename from content/docs/release-notes/release-notes-1.9.md rename to content/docs/releases/release-notes/release-notes-1.9.md diff --git a/content/docs/installation/upgrading/ingress-class-compatibility.md b/content/docs/releases/upgrading/ingress-class-compatibility.md similarity index 100% rename from content/docs/installation/upgrading/ingress-class-compatibility.md rename to content/docs/releases/upgrading/ingress-class-compatibility.md diff --git a/content/docs/installation/upgrading/remove-deprecated-apis.md b/content/docs/releases/upgrading/remove-deprecated-apis.md similarity index 100% rename from content/docs/installation/upgrading/remove-deprecated-apis.md rename to content/docs/releases/upgrading/remove-deprecated-apis.md diff --git a/content/docs/installation/upgrading/upgrading-0.10-0.11.md b/content/docs/releases/upgrading/upgrading-0.10-0.11.md similarity index 98% rename from content/docs/installation/upgrading/upgrading-0.10-0.11.md rename to content/docs/releases/upgrading/upgrading-0.10-0.11.md index 81a85b03d1..913acf51e7 100644 --- a/content/docs/installation/upgrading/upgrading-0.10-0.11.md +++ b/content/docs/releases/upgrading/upgrading-0.10-0.11.md @@ -22,7 +22,7 @@ This upgrade should be performed in a few steps: 1. Back up existing cert-manager resources, as per the [backup and restore guide](../../devops-tips/backup.md). -2. [Uninstall cert-manager](../uninstall.md). +2. [Uninstall cert-manager](../../installation/uninstall.md). 3. Ensure the old cert-manager CRD resources have also been deleted: `kubectl get crd | grep certmanager.k8s.io` @@ -30,7 +30,7 @@ This upgrade should be performed in a few steps: `certmanager.k8s.io/v1alpha1` to `cert-manager.io/v1alpha2`. 5. Re-install cert-manager from scratch according to the [installation - guide](../README.md). + guide](../../installation/upgrade.md). You must be sure to properly **backup**, **uninstall**, **re-install** and **restore** your installation in order to ensure the upgrade is successful. diff --git a/content/docs/installation/upgrading/upgrading-0.11-0.12.md b/content/docs/releases/upgrading/upgrading-0.11-0.12.md similarity index 95% rename from content/docs/installation/upgrading/upgrading-0.11-0.12.md rename to content/docs/releases/upgrading/upgrading-0.11-0.12.md index 582e5b0c4a..a2a8c1da07 100644 --- a/content/docs/installation/upgrading/upgrading-0.11-0.12.md +++ b/content/docs/releases/upgrading/upgrading-0.11-0.12.md @@ -9,7 +9,7 @@ minimal changes that effect end users bar two changes which require action when upgrading. After addressing the following points, you should then follow the standard -upgrade process [here](./README.md). +upgrade process [here](../../installation/upgrade.md). ## Changes to the Vault Kubernetes Auth Mount Path If you are using Kubernetes authentication for Vault `Issuers` then there has diff --git a/content/docs/installation/upgrading/upgrading-0.12-0.13.md b/content/docs/releases/upgrading/upgrading-0.12-0.13.md similarity index 72% rename from content/docs/installation/upgrading/upgrading-0.12-0.13.md rename to content/docs/releases/upgrading/upgrading-0.12-0.13.md index 8fdb8adeb9..7f8ce01e48 100644 --- a/content/docs/installation/upgrading/upgrading-0.12-0.13.md +++ b/content/docs/releases/upgrading/upgrading-0.12-0.13.md @@ -4,4 +4,4 @@ description: 'cert-manager installation: Upgrading v0.12 to v0.13' --- When upgrading from `v0.12` to `v0.13`, no special upgrade steps are required. -Follow the regular upgrade process [here](./README.md). \ No newline at end of file +Follow the regular upgrade process [here](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-0.13-0.14.md b/content/docs/releases/upgrading/upgrading-0.13-0.14.md similarity index 94% rename from content/docs/installation/upgrading/upgrading-0.13-0.14.md rename to content/docs/releases/upgrading/upgrading-0.13-0.14.md index ab170dd47a..22921cae0c 100644 --- a/content/docs/installation/upgrading/upgrading-0.13-0.14.md +++ b/content/docs/releases/upgrading/upgrading-0.13-0.14.md @@ -27,4 +27,4 @@ Version `v0.14` now comes in 2 versions of static manifests, you will need to us The webhook is now a required component, meaning that `no-webhook` variant of the manifests are no longer available in this release. Please use the appropriate manifests as mentioned above according to your Kubernetes version. -From here on you can follow the [regular upgrade process](./README.md). \ No newline at end of file +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-0.14-0.15.md b/content/docs/releases/upgrading/upgrading-0.14-0.15.md similarity index 91% rename from content/docs/installation/upgrading/upgrading-0.14-0.15.md rename to content/docs/releases/upgrading/upgrading-0.14-0.15.md index 1815550067..629e556079 100644 --- a/content/docs/installation/upgrading/upgrading-0.14-0.15.md +++ b/content/docs/releases/upgrading/upgrading-0.14-0.15.md @@ -23,4 +23,4 @@ attached to the GitHub release. You will need to select the appropriate 'legacy' or full manifest variant depending on the Kubernetes or OpenShift version you are running. -From here on you can follow the [regular upgrade process](./README.md). \ No newline at end of file +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-0.15-0.16.md b/content/docs/releases/upgrading/upgrading-0.15-0.16.md similarity index 90% rename from content/docs/installation/upgrading/upgrading-0.15-0.16.md rename to content/docs/releases/upgrading/upgrading-0.15-0.16.md index f6505fdc59..beb13e4459 100644 --- a/content/docs/installation/upgrading/upgrading-0.15-0.16.md +++ b/content/docs/releases/upgrading/upgrading-0.15-0.16.md @@ -13,4 +13,4 @@ Versions of `kubectl` of `v1.15.x` or below are not being supported anymore as t ### Helm Helm users who use `installCRDs=true` MUST upgrade to Helm `v3.3.1` before upgrading. -From here on you can follow the [regular upgrade process](./README.md). \ No newline at end of file +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-0.16-1.0.md b/content/docs/releases/upgrading/upgrading-0.16-1.0.md similarity index 95% rename from content/docs/installation/upgrading/upgrading-0.16-1.0.md rename to content/docs/releases/upgrading/upgrading-0.16-1.0.md index b676bdea2d..fb1043425a 100644 --- a/content/docs/installation/upgrading/upgrading-0.16-1.0.md +++ b/content/docs/releases/upgrading/upgrading-0.16-1.0.md @@ -20,7 +20,7 @@ Helm users who use `installCRDs=true` MUST upgrade to Helm `v3.3.1` or later bef ### Kubernetes `1.16` and above These are the upgrade instructions to upgrade from cert-manager `v0.14.0` or higher, please consult other upgrade guides first before upgrading to `v1.0` if you run an older version of cert-manager. -No special requirements, you can follow the [regular upgrade process](./README.md). +No special requirements, you can follow the [regular upgrade process](../../installation/upgrade.md). ### Kubernetes `1.15.x` @@ -50,7 +50,7 @@ This upgrade MUST be performed in the following sequence of steps: 1. [Back up](../../devops-tips/backup.md) existing cert-manager resources. See the backup section. -2. [Uninstall cert-manager](../uninstall.md). +2. [Uninstall cert-manager](../../installation/uninstall.md). 3. Update the `apiVersion` on all your backed up resources from `cert-manager.io/v1alpha2` to `cert-manager.io/v1`. See the converting section for that. @@ -59,7 +59,7 @@ This upgrade MUST be performed in the following sequence of steps: 5. Re-install cert-manager `v1.0` from scratch according to the [installation - guide](../README.md). + guide](../../installation/upgrade.md). 6. Apply the backed up resources again. @@ -114,10 +114,10 @@ kubectl delete crd issuers.cert-manager.io kubectl delete crd orders.acme.cert-manager.io ``` -For more info see the [uninstall cert-manager guide](../uninstall.md). +For more info see the [uninstall cert-manager guide](../../installation/uninstall.md). #### Reinstall and restore -To install cert-manager again you can follow the normal [installation guide](../README.md). +To install cert-manager again you can follow the normal [installation guide](../../installation/upgrade.md). Once it has been fully installed you can re-apply the converted resources: ```bash diff --git a/content/docs/installation/upgrading/upgrading-0.2-0.3.md b/content/docs/releases/upgrading/upgrading-0.2-0.3.md similarity index 100% rename from content/docs/installation/upgrading/upgrading-0.2-0.3.md rename to content/docs/releases/upgrading/upgrading-0.2-0.3.md diff --git a/content/docs/installation/upgrading/upgrading-0.3-0.4.md b/content/docs/releases/upgrading/upgrading-0.3-0.4.md similarity index 100% rename from content/docs/installation/upgrading/upgrading-0.3-0.4.md rename to content/docs/releases/upgrading/upgrading-0.3-0.4.md diff --git a/content/docs/installation/upgrading/upgrading-0.4-0.5.md b/content/docs/releases/upgrading/upgrading-0.4-0.5.md similarity index 100% rename from content/docs/installation/upgrading/upgrading-0.4-0.5.md rename to content/docs/releases/upgrading/upgrading-0.4-0.5.md diff --git a/content/docs/installation/upgrading/upgrading-0.5-0.6.md b/content/docs/releases/upgrading/upgrading-0.5-0.6.md similarity index 91% rename from content/docs/installation/upgrading/upgrading-0.5-0.6.md rename to content/docs/releases/upgrading/upgrading-0.5-0.6.md index 2b1fa457bb..9fbc721e4a 100644 --- a/content/docs/installation/upgrading/upgrading-0.5-0.6.md +++ b/content/docs/releases/upgrading/upgrading-0.5-0.6.md @@ -25,7 +25,7 @@ Due to issues with the way Helm handles CRD resources in Helm charts, we have now moved the installation of these resources into a separate YAML manifest that must be installed with `kubectl apply` before upgrading the chart. -You can follow the [regular upgrade guide](./README.md) as usual in order to upgrade +You can follow the [regular upgrade guide](../../installation/upgrade.md) as usual in order to upgrade from `v0.5` to `v0.6`. ## Upgrading with static manifests @@ -37,7 +37,7 @@ We now also no longer ship different manifests for different configurations, in favor of a single `cert-manager.yaml` file which should work for all Kubernetes clusters from Kubernetes `v1.9` onward. -You can follow the [regular upgrade guide](./README.md) as usual in order to upgrade from +You can follow the [regular upgrade guide](../../installation/upgrade.md) as usual in order to upgrade from `v0.5` to `v0.6`. ## Upgrading from older versions using Helm @@ -69,7 +69,7 @@ $ kubectl delete crd \ clusterissuers.certmanager.k8s.io ``` -3. Perform a fresh install (as per the [installation guide](../README.md) +3. Perform a fresh install (as per the [installation guide](../../installation/upgrade.md) Install the cert-manager CRDs ```bash diff --git a/content/docs/installation/upgrading/upgrading-0.6-0.7.md b/content/docs/releases/upgrading/upgrading-0.6-0.7.md similarity index 100% rename from content/docs/installation/upgrading/upgrading-0.6-0.7.md rename to content/docs/releases/upgrading/upgrading-0.6-0.7.md diff --git a/content/docs/installation/upgrading/upgrading-0.7-0.8.md b/content/docs/releases/upgrading/upgrading-0.7-0.8.md similarity index 99% rename from content/docs/installation/upgrading/upgrading-0.7-0.8.md rename to content/docs/releases/upgrading/upgrading-0.7-0.8.md index d77a253205..8abf7e506d 100644 --- a/content/docs/installation/upgrading/upgrading-0.7-0.8.md +++ b/content/docs/releases/upgrading/upgrading-0.7-0.8.md @@ -4,7 +4,7 @@ description: 'cert-manager installation: Upgrading v0.7 to v0.8' --- Upgrading from `v0.7` to `v0.8` is possible using the regular [upgrade -guide](./README.md). +guide](../../installation/upgrade.md). All resources should continue to operate as before. diff --git a/content/docs/installation/upgrading/upgrading-0.8-0.9.md b/content/docs/releases/upgrading/upgrading-0.8-0.9.md similarity index 94% rename from content/docs/installation/upgrading/upgrading-0.8-0.9.md rename to content/docs/releases/upgrading/upgrading-0.8-0.9.md index b28ebc9d4f..4adef854be 100644 --- a/content/docs/installation/upgrading/upgrading-0.8-0.9.md +++ b/content/docs/releases/upgrading/upgrading-0.8-0.9.md @@ -18,4 +18,4 @@ $ kubectl delete deployments --namespace cert-manager \ ``` After this operation, follow the standard upgrade process as defined in the -[upgrade guide](./README.md). \ No newline at end of file +[upgrade guide](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-0.9-0.10.md b/content/docs/releases/upgrading/upgrading-0.9-0.10.md similarity index 100% rename from content/docs/installation/upgrading/upgrading-0.9-0.10.md rename to content/docs/releases/upgrading/upgrading-0.9-0.10.md diff --git a/content/docs/installation/upgrading/upgrading-1.0-1.1.md b/content/docs/releases/upgrading/upgrading-1.0-1.1.md similarity index 68% rename from content/docs/installation/upgrading/upgrading-1.0-1.1.md rename to content/docs/releases/upgrading/upgrading-1.0-1.1.md index 9c514a5193..86de90149e 100644 --- a/content/docs/installation/upgrading/upgrading-1.0-1.1.md +++ b/content/docs/releases/upgrading/upgrading-1.0-1.1.md @@ -4,4 +4,4 @@ description: 'cert-manager installation: Upgrading v1.0 to v1.1' --- When upgrading from `v1.0` to `v1.1`, no special upgrade steps are required 🎉. -From here on you can follow the [regular upgrade process](./README.md). \ No newline at end of file +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-1.1-1.2.md b/content/docs/releases/upgrading/upgrading-1.1-1.2.md similarity index 82% rename from content/docs/installation/upgrading/upgrading-1.1-1.2.md rename to content/docs/releases/upgrading/upgrading-1.1-1.2.md index ab7a4e20e1..18877b54ca 100644 --- a/content/docs/installation/upgrading/upgrading-1.1-1.2.md +++ b/content/docs/releases/upgrading/upgrading-1.1-1.2.md @@ -11,11 +11,11 @@ upgrading the legacy `CRD`s to `v1.2`. To solve this, you could replace the `CRD 2. Run `kubectl replace -f https://github.com/cert-manager/cert-manager/releases/download/v1.2.0/cert-manager.crds.yaml` to replace the CRDs. 3. Follow the standard upgrade process. You can read more about supported Kubernetes versions - [here](../supported-releases.md). + [here](../README.md). In this release some features have been deprecated. Please read the [version -1.2 release notes](../../release-notes/release-notes-1.2.md) for more details +1.2 release notes](../../releases/release-notes/release-notes-1.2.md) for more details and consider whether you are using any of these deprecated features before you proceed with the upgrade. -From here on you can follow the [regular upgrade process](./README.md). \ No newline at end of file +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-1.10-1.11.md b/content/docs/releases/upgrading/upgrading-1.10-1.11.md similarity index 91% rename from content/docs/installation/upgrading/upgrading-1.10-1.11.md rename to content/docs/releases/upgrading/upgrading-1.10-1.11.md index ddbfd11e8b..caaf203f1e 100644 --- a/content/docs/installation/upgrading/upgrading-1.10-1.11.md +++ b/content/docs/releases/upgrading/upgrading-1.10-1.11.md @@ -22,4 +22,4 @@ There are additional details in the [Gateway API usage](../../usage/gateway.md) ## Next Steps -From here on you can follow the [regular upgrade process](./README.md). +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). diff --git a/content/docs/installation/upgrading/upgrading-1.11-1.12.md b/content/docs/releases/upgrading/upgrading-1.11-1.12.md similarity index 68% rename from content/docs/installation/upgrading/upgrading-1.11-1.12.md rename to content/docs/releases/upgrading/upgrading-1.11-1.12.md index 1ed3a68f72..78348421c1 100644 --- a/content/docs/installation/upgrading/upgrading-1.11-1.12.md +++ b/content/docs/releases/upgrading/upgrading-1.11-1.12.md @@ -7,4 +7,4 @@ There are no breaking changes between cert-manager 1.11 and 1.12. ## Next Steps -From here on you can follow the [regular upgrade process](./README.md). +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). diff --git a/content/docs/installation/upgrading/upgrading-1.12-1.13.md b/content/docs/releases/upgrading/upgrading-1.12-1.13.md similarity index 93% rename from content/docs/installation/upgrading/upgrading-1.12-1.13.md rename to content/docs/releases/upgrading/upgrading-1.12-1.13.md index f928868455..859ff22987 100644 --- a/content/docs/installation/upgrading/upgrading-1.12-1.13.md +++ b/content/docs/releases/upgrading/upgrading-1.12-1.13.md @@ -16,4 +16,4 @@ this will now break (unless the webhook actually has a feature by that name). (h ## Next Steps -From here on you can follow the [regular upgrade process](./README.md). +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). diff --git a/content/docs/installation/upgrading/upgrading-1.2-1.3.md b/content/docs/releases/upgrading/upgrading-1.2-1.3.md similarity index 94% rename from content/docs/installation/upgrading/upgrading-1.2-1.3.md rename to content/docs/releases/upgrading/upgrading-1.2-1.3.md index 2aa5394fc8..a7a56e390b 100644 --- a/content/docs/installation/upgrading/upgrading-1.2-1.3.md +++ b/content/docs/releases/upgrading/upgrading-1.2-1.3.md @@ -29,4 +29,4 @@ This means users will need to create an Application in `OutagePREDICT` and assoc ## Next Steps -You should now follow the [regular upgrade process](./README.md). \ No newline at end of file +You should now follow the [regular upgrade process](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-1.3-1.4.md b/content/docs/releases/upgrading/upgrading-1.3-1.4.md similarity index 86% rename from content/docs/installation/upgrading/upgrading-1.3-1.4.md rename to content/docs/releases/upgrading/upgrading-1.3-1.4.md index 71dce12965..d7befe89e1 100644 --- a/content/docs/installation/upgrading/upgrading-1.3-1.4.md +++ b/content/docs/releases/upgrading/upgrading-1.3-1.4.md @@ -24,8 +24,8 @@ and which is therefore available on Please uninstall the existing cert-manager package and re-install by following the [OLM Installation Documentation][]. -[OLM Installation Documentation]: ../operator-lifecycle-manager.md +[OLM Installation Documentation]: ../../installation/operator-lifecycle-manager.md ## Now Follow the Regular Upgrade Process -From here on you can follow the [regular upgrade process](./README.md). \ No newline at end of file +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-1.4-1.5.md b/content/docs/releases/upgrading/upgrading-1.4-1.5.md similarity index 82% rename from content/docs/installation/upgrading/upgrading-1.4-1.5.md rename to content/docs/releases/upgrading/upgrading-1.4-1.5.md index f991809419..46385192ce 100644 --- a/content/docs/installation/upgrading/upgrading-1.4-1.5.md +++ b/content/docs/releases/upgrading/upgrading-1.4-1.5.md @@ -8,4 +8,4 @@ notes to see if your Ingress controller has any known issues with the migration ## Now Follow the Regular Upgrade Process -From here on you can follow the [regular upgrade process](./README.md). \ No newline at end of file +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-1.5-1.6.md b/content/docs/releases/upgrading/upgrading-1.5-1.6.md similarity index 93% rename from content/docs/installation/upgrading/upgrading-1.5-1.6.md rename to content/docs/releases/upgrading/upgrading-1.5-1.6.md index 4524d702db..e819e77947 100644 --- a/content/docs/installation/upgrading/upgrading-1.5-1.6.md +++ b/content/docs/releases/upgrading/upgrading-1.5-1.6.md @@ -27,4 +27,4 @@ notes to see if your Ingress controller has any known issues with the migration ## Now Follow the Regular Upgrade Process -From here on you can follow the [regular upgrade process](./README.md). +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). diff --git a/content/docs/installation/upgrading/upgrading-1.6-1.7.md b/content/docs/releases/upgrading/upgrading-1.6-1.7.md similarity index 92% rename from content/docs/installation/upgrading/upgrading-1.6-1.7.md rename to content/docs/releases/upgrading/upgrading-1.6-1.7.md index d53d1eaf81..4b5e012dd4 100644 --- a/content/docs/installation/upgrading/upgrading-1.6-1.7.md +++ b/content/docs/releases/upgrading/upgrading-1.6-1.7.md @@ -21,4 +21,4 @@ on supported versions before `v1.22`. ## Now Follow the Regular Upgrade Process -From here on you can follow the [regular upgrade process](./README.md). \ No newline at end of file +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). \ No newline at end of file diff --git a/content/docs/installation/upgrading/upgrading-1.7-1.8.md b/content/docs/releases/upgrading/upgrading-1.7-1.8.md similarity index 97% rename from content/docs/installation/upgrading/upgrading-1.7-1.8.md rename to content/docs/releases/upgrading/upgrading-1.7-1.8.md index 1faec63bf2..b65538b5ba 100644 --- a/content/docs/installation/upgrading/upgrading-1.7-1.8.md +++ b/content/docs/releases/upgrading/upgrading-1.7-1.8.md @@ -88,4 +88,4 @@ and add the `parentRefs`: ## Now, Follow the Regular Upgrade Process -From here on you can follow the [regular upgrade process](./README.md). +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). diff --git a/content/docs/installation/upgrading/upgrading-1.8-1.9.md b/content/docs/releases/upgrading/upgrading-1.8-1.9.md similarity index 80% rename from content/docs/installation/upgrading/upgrading-1.8-1.9.md rename to content/docs/releases/upgrading/upgrading-1.8-1.9.md index b45f6cfb3c..5c61e8293a 100644 --- a/content/docs/installation/upgrading/upgrading-1.8-1.9.md +++ b/content/docs/releases/upgrading/upgrading-1.8-1.9.md @@ -8,4 +8,4 @@ If running Kubernetes versions before `v1.22`, the feature gate _must_ be enabled in the cluster. This beta feature is enabled by default on supported versions before `v1.22`. -From here on you can follow the [regular upgrade process](./README.md). +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). diff --git a/content/docs/installation/upgrading/upgrading-1.9-1.10.md b/content/docs/releases/upgrading/upgrading-1.9-1.10.md similarity index 81% rename from content/docs/installation/upgrading/upgrading-1.9-1.10.md rename to content/docs/releases/upgrading/upgrading-1.9-1.10.md index 4a4f1310ec..76695893ba 100644 --- a/content/docs/installation/upgrading/upgrading-1.9-1.10.md +++ b/content/docs/releases/upgrading/upgrading-1.9-1.10.md @@ -10,8 +10,8 @@ is set to `RuntimeDefault`. On some versions and configurations of OpenShift this can cause the Pod to be rejected by the [Security Context Constraints admission webhook](https://docs.openshift.com/container-platform/4.10/authentication/managing-security-context-constraints.html#admission_configuring-internal-oauth). -> 📖 Read the [Breaking Changes section in the 1.10 release notes](../../release-notes/release-notes-1.10.md) before upgrading. +> 📖 Read the [Breaking Changes section in the 1.10 release notes](../../releases/release-notes/release-notes-1.10.md) before upgrading. ## Next Steps -From here on you can follow the [regular upgrade process](./README.md). +From here on you can follow the [regular upgrade process](../../installation/upgrade.md). diff --git a/public/_redirects b/public/_redirects index 2778abd2a5..aedf467951 100644 --- a/public/_redirects +++ b/public/_redirects @@ -204,3 +204,8 @@ https://docs.cert-manager.io/* https://cert-manager.io/docs/:splat 302! /docs/projects/trust-manager/api-reference/ /docs/trust/trust-manager/api-reference/ 301! /docs/projects/approver-policy/ /docs/policy/approval/approver-policy/ 301! /docs/projects/approver-policy/api-reference/ /docs/policy/approval/approver-policy/api-reference/ 301! + +# Moved the "upgrading" and "release-notes" pages to the release section +/docs/installation/upgrading/* /docs/releases/upgrading/:splat 301! +/docs/release-notes/* /docs/releases/release-notes/:splat 301! +/docs/installation/supported-releases/ /docs/releases/ 301! From aaf638f956af68621dd1f18072ca978ea42b1b49 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 5 Oct 2023 19:17:09 +0200 Subject: [PATCH 183/264] rename 'releases' menu items Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/manifest.json | 116 ++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/content/docs/manifest.json b/content/docs/manifest.json index d25c428093..4cacaff146 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -20,19 +20,19 @@ "path": "/docs/releases/README.md" }, { - "title": "v1.13", + "title": "1.13", "path": "/docs/releases/release-notes/release-notes-1.13.md" }, { - "title": "upgrade v1.12 to v1.13", + "title": "upgrade 1.12 to 1.13", "path": "/docs/releases/upgrading/upgrading-1.12-1.13.md" }, { - "title": "v1.12", + "title": "1.12", "path": "/docs/releases/release-notes/release-notes-1.12.md" }, { - "title": "upgrade v1.11 to v1.12", + "title": "upgrade 1.11 to 1.12", "path": "/docs/releases/upgrading/upgrading-1.11-1.12.md" }, { @@ -47,219 +47,219 @@ "path": "/docs/releases/upgrading/remove-deprecated-apis.md" }, { - "title": "v1.11", + "title": "1.11", "path": "/docs/releases/release-notes/release-notes-1.11.md" }, { - "title": "upgrade v1.10 to v1.11", + "title": "upgrade 1.10 to 1.11", "path": "/docs/releases/upgrading/upgrading-1.10-1.11.md" }, { - "title": "v1.10", + "title": "1.10", "path": "/docs/releases/release-notes/release-notes-1.10.md" }, { - "title": "upgrade v1.9 to v1.10", + "title": "upgrade 1.9 to 1.10", "path": "/docs/releases/upgrading/upgrading-1.9-1.10.md" }, { - "title": "v1.9", + "title": "1.9", "path": "/docs/releases/release-notes/release-notes-1.9.md" }, { - "title": "upgrade v1.8 to v1.9", + "title": "upgrade 1.8 to 1.9", "path": "/docs/releases/upgrading/upgrading-1.8-1.9.md" }, { - "title": "v1.8", + "title": "1.8", "path": "/docs/releases/release-notes/release-notes-1.8.md" }, { - "title": "upgrade v1.7 to v1.8", + "title": "upgrade 1.7 to 1.8", "path": "/docs/releases/upgrading/upgrading-1.7-1.8.md" }, { - "title": "v1.7", + "title": "1.7", "path": "/docs/releases/release-notes/release-notes-1.7.md" }, { - "title": "upgrade v1.6 to v1.7", + "title": "upgrade 1.6 to 1.7", "path": "/docs/releases/upgrading/upgrading-1.6-1.7.md" }, { - "title": "v1.6", + "title": "1.6", "path": "/docs/releases/release-notes/release-notes-1.6.md" }, { - "title": "upgrade v1.5 to v1.6", + "title": "upgrade 1.5 to 1.6", "path": "/docs/releases/upgrading/upgrading-1.5-1.6.md" }, { - "title": "v1.5", + "title": "1.5", "path": "/docs/releases/release-notes/release-notes-1.5.md" }, { - "title": "upgrade v1.4 to v1.5", + "title": "upgrade 1.4 to 1.5", "path": "/docs/releases/upgrading/upgrading-1.4-1.5.md" }, { - "title": "v1.4", + "title": "1.4", "path": "/docs/releases/release-notes/release-notes-1.4.md" }, { - "title": "upgrade v1.3 to v1.4", + "title": "upgrade 1.3 to 1.4", "path": "/docs/releases/upgrading/upgrading-1.3-1.4.md" }, { - "title": "v1.3", + "title": "1.3", "path": "/docs/releases/release-notes/release-notes-1.3.md" }, { - "title": "upgrade v1.2 to v1.3", + "title": "upgrade 1.2 to 1.3", "path": "/docs/releases/upgrading/upgrading-1.2-1.3.md" }, { - "title": "v1.2", + "title": "1.2", "path": "/docs/releases/release-notes/release-notes-1.2.md" }, { - "title": "upgrade v1.1 to v1.2", + "title": "upgrade 1.1 to 1.2", "path": "/docs/releases/upgrading/upgrading-1.1-1.2.md" }, { - "title": "v1.1", + "title": "1.1", "path": "/docs/releases/release-notes/release-notes-1.1.md" }, { - "title": "upgrade v1.0 to v1.1", + "title": "upgrade 1.0 to 1.1", "path": "/docs/releases/upgrading/upgrading-1.0-1.1.md" }, { - "title": "v1.0", + "title": "1.0", "path": "/docs/releases/release-notes/release-notes-1.0.md" }, { - "title": "upgrade v0.16 to v1.0", + "title": "upgrade 0.16 to 1.0", "path": "/docs/releases/upgrading/upgrading-0.16-1.0.md" }, { - "title": "v0.16", + "title": "0.16", "path": "/docs/releases/release-notes/release-notes-0.16.md" }, { - "title": "upgrade v0.15 to v0.16", + "title": "upgrade 0.15 to 0.16", "path": "/docs/releases/upgrading/upgrading-0.15-0.16.md" }, { - "title": "v0.15", + "title": "0.15", "path": "/docs/releases/release-notes/release-notes-0.15.md" }, { - "title": "upgrade v0.14 to v0.15", + "title": "upgrade 0.14 to 0.15", "path": "/docs/releases/upgrading/upgrading-0.14-0.15.md" }, { - "title": "v0.14", + "title": "0.14", "path": "/docs/releases/release-notes/release-notes-0.14.md" }, { - "title": "upgrade v0.13 to v0.14", + "title": "upgrade 0.13 to 0.14", "path": "/docs/releases/upgrading/upgrading-0.13-0.14.md" }, { - "title": "v0.13", + "title": "0.13", "path": "/docs/releases/release-notes/release-notes-0.13.md" }, { - "title": "upgrade v0.12 to v0.13", + "title": "upgrade 0.12 to 0.13", "path": "/docs/releases/upgrading/upgrading-0.12-0.13.md" }, { - "title": "v0.12", + "title": "0.12", "path": "/docs/releases/release-notes/release-notes-0.12.md" }, { - "title": "upgrade v0.11 to v0.12", + "title": "upgrade 0.11 to 0.12", "path": "/docs/releases/upgrading/upgrading-0.11-0.12.md" }, { - "title": "v0.11", + "title": "0.11", "path": "/docs/releases/release-notes/release-notes-0.11.md" }, { - "title": "upgrade v0.10 to v0.11", + "title": "upgrade 0.10 to 0.11", "path": "/docs/releases/upgrading/upgrading-0.10-0.11.md" }, { - "title": "v0.10", + "title": "0.10", "path": "/docs/releases/release-notes/release-notes-0.10.md" }, { - "title": "upgrade v0.9 to v0.10", + "title": "upgrade 0.9 to 0.10", "path": "/docs/releases/upgrading/upgrading-0.9-0.10.md" }, { - "title": "v0.9", + "title": "0.9", "path": "/docs/releases/release-notes/release-notes-0.9.md" }, { - "title": "upgrade v0.8 to v0.9", + "title": "upgrade 0.8 to 0.9", "path": "/docs/releases/upgrading/upgrading-0.8-0.9.md" }, { - "title": "v0.8", + "title": "0.8", "path": "/docs/releases/release-notes/release-notes-0.8.md" }, { - "title": "upgrade v0.7 to v0.8", + "title": "upgrade 0.7 to 0.8", "path": "/docs/releases/upgrading/upgrading-0.7-0.8.md" }, { - "title": "v0.7", + "title": "0.7", "path": "/docs/releases/release-notes/release-notes-0.7.md" }, { - "title": "upgrade v0.6 to v0.7", + "title": "upgrade 0.6 to 0.7", "path": "/docs/releases/upgrading/upgrading-0.6-0.7.md" }, { - "title": "v0.6", + "title": "0.6", "path": "/docs/releases/release-notes/release-notes-0.6.md" }, { - "title": "upgrade v0.5 to v0.6", + "title": "upgrade 0.5 to 0.6", "path": "/docs/releases/upgrading/upgrading-0.5-0.6.md" }, { - "title": "v0.5", + "title": "0.5", "path": "/docs/releases/release-notes/release-notes-0.5.md" }, { - "title": "upgrade v0.4 to v0.5", + "title": "upgrade 0.4 to 0.5", "path": "/docs/releases/upgrading/upgrading-0.4-0.5.md" }, { - "title": "v0.4", + "title": "0.4", "path": "/docs/releases/release-notes/release-notes-0.4.md" }, { - "title": "upgrade v0.3 to v0.4", + "title": "upgrade 0.3 to 0.4", "path": "/docs/releases/upgrading/upgrading-0.3-0.4.md" }, { - "title": "v0.3", + "title": "0.3", "path": "/docs/releases/release-notes/release-notes-0.3.md" }, { - "title": "upgrade v0.2 to v0.3", + "title": "upgrade 0.2 to 0.3", "path": "/docs/releases/upgrading/upgrading-0.2-0.3.md" }, { - "title": "v0.2", + "title": "0.2", "path": "/docs/releases/release-notes/release-notes-0.2.md" }, { - "title": "v0.1", + "title": "0.1", "path": "/docs/releases/release-notes/release-notes-0.1.md" } ] From 8f82a55e231e27158c0abd8577ee72d0c51cf3eb Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:13:00 +0200 Subject: [PATCH 184/264] use best-practice link format Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/contributing/release-process.md | 2 +- content/docs/installation/helm.md | 2 +- content/docs/installation/kubectl.md | 2 +- content/docs/installation/operator-lifecycle-manager.md | 2 +- content/docs/releases/release-notes/release-notes-1.2.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 3b00ebcae0..6d11c94d2c 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -189,7 +189,7 @@ page if a step is missing or if it is outdated. document is ready to be merged on [cert-manager/website](https://github.com/cert-manager/website). See for example, see - [upgrading-1.0-1.1](https://cert-manager.io/docs/releases/upgrading/upgrading-1.0-1.1/). + [upgrading-1.0-1.1](https://cert-manager.io/docs/releases/upgrading/upgrading-1.0-1.1.md). 4. **(final + patch releases)** Prepare the Website "Release Notes" PR. diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index 1df1bcd0bb..015c9aa875 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -13,7 +13,7 @@ non-namespaced resources in your cluster and care must be taken to ensure that i ### Prerequisites - [Install Helm version 3 or later](https://helm.sh/docs/intro/install/). -- Install a [supported version of Kubernetes or OpenShift](../releases/). +- Install a [supported version of Kubernetes or OpenShift](../releases/README.md). - Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. ### Steps diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index f5c4bfaaef..5f4caf8d7c 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -8,7 +8,7 @@ Learn how to install cert-manager using kubectl and static manifests. ## Prerequisites - [Install `kubectl` version `>= v1.19.0`](https://kubernetes.io/docs/tasks/tools/). (otherwise, you'll have issues updating the CRDs - see [v0.16 upgrade notes](../releases/upgrading/upgrading-0.15-0.16.md#issue-with-older-versions-of-kubectl)) -- Install a [supported version of Kubernetes or OpenShift](../releases/). +- Install a [supported version of Kubernetes or OpenShift](../releases/README.md). - Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. ## Steps diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index 04328f4f72..2b7b57d1b2 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -7,7 +7,7 @@ description: 'cert-manager installation: Using OLM' ### Prerequisites -- Install a [supported version of Kubernetes or OpenShift](../releases/). +- Install a [supported version of Kubernetes or OpenShift](../releases/README.md). - Read [Compatibility with Kubernetes Platform Providers](./compatibility.md) if you are using Kubernetes on a cloud platform. ### Option 1: Installing from OperatorHub Web Console on OpenShift diff --git a/content/docs/releases/release-notes/release-notes-1.2.md b/content/docs/releases/release-notes/release-notes-1.2.md index 1823eb932e..12cc382d93 100644 --- a/content/docs/releases/release-notes/release-notes-1.2.md +++ b/content/docs/releases/release-notes/release-notes-1.2.md @@ -15,7 +15,7 @@ Aside from that, there have been numerous bug fixes and features summarized belo 1. The `--renew-before-expiration-duration` flag of the cert-manager controller-manager has been deprecated. Please set the `Certificate.Spec.RenewBefore` field instead. This flag will be removed in the next release. -2. As Kubernetes `v1.16` is now the earliest supported version, The `legacy` manifests have now been removed. You can read more [here](../). +2. As Kubernetes `v1.16` is now the earliest supported version, The `legacy` manifests have now been removed. You can read more [here](../README.md). 3. The `User-Agent` request header has been changed from `jetstack-cert-manager/` to `cert-manager/`. This may affect functionality if you rely on an a User-Agent allowlist in a corporate environment. From 2087053dc8661f69a66ceb2d8dc7e3b26df11123 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:39:33 +0200 Subject: [PATCH 185/264] fix remaining feedback Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/contributing/contributing-flow.md | 2 +- content/docs/contributing/release-process.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/contributing/contributing-flow.md b/content/docs/contributing/contributing-flow.md index 3cd1e50b41..7dc1c0d02a 100644 --- a/content/docs/contributing/contributing-flow.md +++ b/content/docs/contributing/contributing-flow.md @@ -117,7 +117,7 @@ If this PR introduces a breaking change, the release note block must start with ### Cherry Picking If the pull request contains a critical bug fix then this should be cherry picked in to the current stable cert-manager branch -and [released as a patch release](../releases#support-policy). +and [released as a patch release](../releases/README.md#support-policy). To trigger the cherry-pick process, add a comment to the GitHub PR. For example: diff --git a/content/docs/contributing/release-process.md b/content/docs/contributing/release-process.md index 6d11c94d2c..b968b292d4 100644 --- a/content/docs/contributing/release-process.md +++ b/content/docs/contributing/release-process.md @@ -6,7 +6,7 @@ description: 'cert-manager contributing: Release process' This document aims to outline the process that should be followed for cutting a new release of cert-manager. If you would like to know more about current releases and the timeline for future releases, take a look at the -[Supported Releases](../releases) page. +[Supported Releases](../releases/README.md) page. ## Prerequisites @@ -246,10 +246,10 @@ page if a step is missing or if it is outdated. In that PR: 1. (**final release**) Update the section "Supported releases" in the - [supported-releases](../releases) page. + [supported-releases](../releases/README.md) page. 2. (**final release**) Update the section "How we determine supported Kubernetes versions" on the - [supported-releases](../releases) page. + [supported-releases](../releases/README.md) page. 3. (**final release**) Bump the version that appears in `scripts/gendocs/generate-new-import-path-docs`. For example: From abd0be898dbdbcdcefe5d8225b4c540be3df1abc Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 6 Oct 2023 15:12:09 +0200 Subject: [PATCH 186/264] fix spelling alerts because links are splits over multiple lines Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 1 - content/docs/releases/upgrading/upgrading-0.10-0.11.md | 4 ++-- content/docs/releases/upgrading/upgrading-0.16-1.0.md | 4 ++-- content/docs/releases/upgrading/upgrading-0.7-0.8.md | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.spelling b/.spelling index 3bd4c0ab5a..6a2e900f0f 100644 --- a/.spelling +++ b/.spelling @@ -666,7 +666,6 @@ README.md certificate.md pageinfo md#renew -upgrade.md # END TEMPORARY diff --git a/content/docs/releases/upgrading/upgrading-0.10-0.11.md b/content/docs/releases/upgrading/upgrading-0.10-0.11.md index 913acf51e7..183d9fc643 100644 --- a/content/docs/releases/upgrading/upgrading-0.10-0.11.md +++ b/content/docs/releases/upgrading/upgrading-0.10-0.11.md @@ -29,8 +29,8 @@ This upgrade should be performed in a few steps: 4. Update the `apiVersion` on all your backed up resources from `certmanager.k8s.io/v1alpha1` to `cert-manager.io/v1alpha2`. -5. Re-install cert-manager from scratch according to the [installation - guide](../../installation/upgrade.md). +5. Re-install cert-manager from scratch according to the + [installation guide](../../installation/upgrade.md). You must be sure to properly **backup**, **uninstall**, **re-install** and **restore** your installation in order to ensure the upgrade is successful. diff --git a/content/docs/releases/upgrading/upgrading-0.16-1.0.md b/content/docs/releases/upgrading/upgrading-0.16-1.0.md index fb1043425a..54fd19eb24 100644 --- a/content/docs/releases/upgrading/upgrading-0.16-1.0.md +++ b/content/docs/releases/upgrading/upgrading-0.16-1.0.md @@ -58,8 +58,8 @@ This upgrade MUST be performed in the following sequence of steps: 4. Ensure the old cert-manager CRD resources have also been deleted: `kubectl get crd | grep cert-manager.io` -5. Re-install cert-manager `v1.0` from scratch according to the [installation - guide](../../installation/upgrade.md). +5. Re-install cert-manager `v1.0` from scratch according to the + [installation guide](../../installation/upgrade.md). 6. Apply the backed up resources again. diff --git a/content/docs/releases/upgrading/upgrading-0.7-0.8.md b/content/docs/releases/upgrading/upgrading-0.7-0.8.md index 8abf7e506d..084e1487a0 100644 --- a/content/docs/releases/upgrading/upgrading-0.7-0.8.md +++ b/content/docs/releases/upgrading/upgrading-0.7-0.8.md @@ -3,8 +3,8 @@ title: Upgrading from v0.7 to v0.8 description: 'cert-manager installation: Upgrading v0.7 to v0.8' --- -Upgrading from `v0.7` to `v0.8` is possible using the regular [upgrade -guide](../../installation/upgrade.md). +Upgrading from `v0.7` to `v0.8` is possible using the regular +[upgrade guide](../../installation/upgrade.md). All resources should continue to operate as before. From 8ccb7cb16f865eb01fd6c38b1c4a0258c285270c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:55:27 +0000 Subject: [PATCH 187/264] build(deps): bump postcss and next Bumps [postcss](https://github.com/postcss/postcss) to 8.4.31 and updates ancestor dependencies [postcss](https://github.com/postcss/postcss) and [next](https://github.com/vercel/next.js). These dependencies need to be updated together. Updates `postcss` from 8.4.14 to 8.4.31 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.14...8.4.31) Updates `postcss` from 8.4.21 to 8.4.31 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.14...8.4.31) Updates `next` from 13.1.2 to 13.5.4 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.1.2...v13.5.4) --- updated-dependencies: - dependency-name: postcss dependency-type: indirect - dependency-name: postcss dependency-type: indirect - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 419 ++++++++++++++++++---------------------------- package.json | 2 +- 2 files changed, 166 insertions(+), 255 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf05b43585..4e7a1a3eb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "classnames": "2.3.2", "compare-versions": "6.0.0-rc.1", "gray-matter": "4.0.3", - "next": "13.1.2", + "next": "13.5.4", "next-mdx-remote": "4.2.0", "next-seo": "5.15.0", "prism-react-renderer": "1.3.5", @@ -988,9 +988,9 @@ } }, "node_modules/@next/env": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.2.tgz", - "integrity": "sha512-PpT4UZIX66VMTqXt4HKEJ+/PwbS+tWmmhZlazaws1a+dbUA5pPdjntQ46Jvj616i3ZKN9doS9LHx3y50RLjAWg==" + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.4.tgz", + "integrity": "sha512-LGegJkMvRNw90WWphGJ3RMHMVplYcOfRWf2Be3td3sUa+1AaxmsYyANsA+znrGCBjXJNi4XAQlSoEfUxs/4kIQ==" }, "node_modules/@next/eslint-plugin-next": { "version": "13.1.2", @@ -1013,40 +1013,10 @@ "@mdx-js/react": "*" } }, - "node_modules/@next/swc-android-arm-eabi": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.2.tgz", - "integrity": "sha512-7mRz1owoGsbfIcdOJA3kk7KEwPZ+OvVT1z9DkR/yru4QdVLF69h/1SHy0vlUNQMxDRllabhxCfkoZCB34GOGAg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-android-arm64": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.2.tgz", - "integrity": "sha512-mgjZ2eJSayovQm1LcE54BLSI4jjnnnLtq5GY5g+DdPuUiCT644gKtjZ/w2BQvuIecCqqBO+Ph9yzo/wUTq7NLg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.2.tgz", - "integrity": "sha512-RikoQqy109r2222UJlyGs4dZw2BibkfPqpeFdW5JEGv+L2PStlHID8DwyVYbmHfQ0VIBGvbf/NAUtFakAWlhwg==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.4.tgz", + "integrity": "sha512-Df8SHuXgF1p+aonBMcDPEsaahNo2TCwuie7VXED4FVyECvdXfRT9unapm54NssV9tF3OQFKBFOdlje4T43VO0w==", "cpu": [ "arm64" ], @@ -1059,9 +1029,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.2.tgz", - "integrity": "sha512-JbDZjaTvL8gyPC5TAH6OnD4jmXPkyUxRYPvu08ZmhT/XAFBb/Cso0BdXyDax/BPCG70mimP9d3hXNKNq+A0VtQ==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.4.tgz", + "integrity": "sha512-siPuUwO45PnNRMeZnSa8n/Lye5ZX93IJom9wQRB5DEOdFrw0JjOMu1GINB8jAEdwa7Vdyn1oJ2xGNaQpdQQ9Pw==", "cpu": [ "x64" ], @@ -1073,40 +1043,10 @@ "node": ">= 10" } }, - "node_modules/@next/swc-freebsd-x64": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.2.tgz", - "integrity": "sha512-ax4j8VrdFQ/xc3W7Om0u1vnDxVApQHKsChBbAMynCrnycZmpbqK4MZu4ZkycT+mx2eccCiqZROpbzDbEdPosEw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm-gnueabihf": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.2.tgz", - "integrity": "sha512-NcRHTesnCxnUvSJa637PQJffBBkmqi5XS/xVWGY7dI6nyJ+pC96Oj7kd+mcjnFUQI5lHKbg39qBWKtOzbezc4w==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.2.tgz", - "integrity": "sha512-AxJdjocLtPrsBY4P2COSBIc3crT5bpjgGenNuINoensOlXhBkYM0aRDYZdydwXOhG+kN2ngUvfgitop9pa204w==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.4.tgz", + "integrity": "sha512-l/k/fvRP/zmB2jkFMfefmFkyZbDkYW0mRM/LB+tH5u9pB98WsHXC0WvDHlGCYp3CH/jlkJPL7gN8nkTQVrQ/2w==", "cpu": [ "arm64" ], @@ -1119,9 +1059,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.2.tgz", - "integrity": "sha512-JmNimDkcCRq7P5zpkdqeaSZ69qKDntEPtyIaMNWqy5M0WUJxGim0Fs6Qzxayiyvuuh9Guxks4woQ/j/ZvX/c8Q==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.4.tgz", + "integrity": "sha512-YYGb7SlLkI+XqfQa8VPErljb7k9nUnhhRrVaOdfJNCaQnHBcvbT7cx/UjDQLdleJcfyg1Hkn5YSSIeVfjgmkTg==", "cpu": [ "arm64" ], @@ -1134,9 +1074,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.2.tgz", - "integrity": "sha512-TsLsjZwUlgmvI42neTuIoD6K9RlXCUzqPtvIClgXxVO0um0DiZwK+M+0zX/uVXhMVphfPY2c5YeR1zFSIONY4A==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.4.tgz", + "integrity": "sha512-uE61vyUSClnCH18YHjA8tE1prr/PBFlBFhxBZis4XBRJoR+txAky5d7gGNUIbQ8sZZ7LVkSVgm/5Fc7mwXmRAg==", "cpu": [ "x64" ], @@ -1149,9 +1089,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.2.tgz", - "integrity": "sha512-eSkyXgCXydEFPTkcncQOGepafedPte6JT/OofB9uvruucrrMVBagCASOuPxodWEMrlfEKSXVnExMKIlfmQMD7A==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.4.tgz", + "integrity": "sha512-qVEKFYML/GvJSy9CfYqAdUexA6M5AklYcQCW+8JECmkQHGoPxCf04iMh7CPR7wkHyWWK+XLt4Ja7hhsPJtSnhg==", "cpu": [ "x64" ], @@ -1164,9 +1104,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.2.tgz", - "integrity": "sha512-DmXFaRTgt2KrV9dmRLifDJE+cYiutHVFIw5/C9BtnwXH39uf3YbPxeD98vNrtqqqZVVLXY/1ySaSIwzYnqeY9g==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.4.tgz", + "integrity": "sha512-mDSQfqxAlfpeZOLPxLymZkX0hYF3juN57W6vFHTvwKlnHfmh12Pt7hPIRLYIShk8uYRsKPtMTth/EzpwRI+u8w==", "cpu": [ "arm64" ], @@ -1179,9 +1119,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.2.tgz", - "integrity": "sha512-3+nBkuFs/wT+lmRVQNH5SyDT7I4vUlNPntosEaEP63FuYQdPLaxz0GvcR66MdFSFh2fsvazpe4wciOwVS4FItQ==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.4.tgz", + "integrity": "sha512-aoqAT2XIekIWoriwzOmGFAvTtVY5O7JjV21giozBTP5c6uZhpvTWRbmHXbmsjZqY4HnEZQRXWkSAppsIBweKqw==", "cpu": [ "ia32" ], @@ -1194,9 +1134,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.2.tgz", - "integrity": "sha512-avsyveEvcvH42PvKjR4Pb8JlLttuGURr2H3ZhS2b85pHOiZ7yjH3rMUoGnNzuLMApyxYaCvd4MedPrLhnNhkog==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.4.tgz", + "integrity": "sha512-cyRvlAxwlddlqeB9xtPSfNSCRy8BOa4wtMo0IuI9P7Y0XT2qpDrpFKRyZ7kUngZis59mPVla5k8X1oOJ8RxDYg==", "cpu": [ "x64" ], @@ -1361,9 +1301,9 @@ "dev": true }, "node_modules/@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", "dependencies": { "tslib": "^2.4.0" } @@ -2240,6 +2180,17 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -4246,8 +4197,7 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "peer": true + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/global-modules": { "version": "2.0.0", @@ -7547,9 +7497,15 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -7602,49 +7558,43 @@ "peer": true }, "node_modules/next": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/next/-/next-13.1.2.tgz", - "integrity": "sha512-Rdnnb2YH///w78FEOR/IQ6TXga+qpth4OqFSem48ng1PYYKr6XBsIk1XVaRcIGM3o6iiHnun0nJvkJHDf+ICyQ==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/next/-/next-13.5.4.tgz", + "integrity": "sha512-+93un5S779gho8y9ASQhb/bTkQF17FNQOtXLKAj3lsNgltEcF0C5PMLLncDmH+8X1EnJH1kbqAERa29nRXqhjA==", "dependencies": { - "@next/env": "13.1.2", - "@swc/helpers": "0.4.14", + "@next/env": "13.5.4", + "@swc/helpers": "0.5.2", + "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", - "postcss": "8.4.14", - "styled-jsx": "5.1.1" + "postcss": "8.4.31", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=14.6.0" + "node": ">=16.14.0" }, "optionalDependencies": { - "@next/swc-android-arm-eabi": "13.1.2", - "@next/swc-android-arm64": "13.1.2", - "@next/swc-darwin-arm64": "13.1.2", - "@next/swc-darwin-x64": "13.1.2", - "@next/swc-freebsd-x64": "13.1.2", - "@next/swc-linux-arm-gnueabihf": "13.1.2", - "@next/swc-linux-arm64-gnu": "13.1.2", - "@next/swc-linux-arm64-musl": "13.1.2", - "@next/swc-linux-x64-gnu": "13.1.2", - "@next/swc-linux-x64-musl": "13.1.2", - "@next/swc-win32-arm64-msvc": "13.1.2", - "@next/swc-win32-ia32-msvc": "13.1.2", - "@next/swc-win32-x64-msvc": "13.1.2" + "@next/swc-darwin-arm64": "13.5.4", + "@next/swc-darwin-x64": "13.5.4", + "@next/swc-linux-arm64-gnu": "13.5.4", + "@next/swc-linux-arm64-musl": "13.5.4", + "@next/swc-linux-x64-gnu": "13.5.4", + "@next/swc-linux-x64-musl": "13.5.4", + "@next/swc-win32-arm64-msvc": "13.5.4", + "@next/swc-win32-ia32-msvc": "13.5.4", + "@next/swc-win32-x64-msvc": "13.5.4" }, "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^6.0.0 || ^7.0.0", + "@opentelemetry/api": "^1.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { + "@opentelemetry/api": { "optional": true }, "sass": { @@ -8202,9 +8152,9 @@ } }, "node_modules/postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -8213,10 +8163,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -10016,6 +9970,14 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -10349,30 +10311,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stylelint/node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - } - ], - "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/stylelint/node_modules/postcss-safe-parser": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", @@ -11477,7 +11415,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -12452,9 +12389,9 @@ } }, "@next/env": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.2.tgz", - "integrity": "sha512-PpT4UZIX66VMTqXt4HKEJ+/PwbS+tWmmhZlazaws1a+dbUA5pPdjntQ46Jvj616i3ZKN9doS9LHx3y50RLjAWg==" + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.4.tgz", + "integrity": "sha512-LGegJkMvRNw90WWphGJ3RMHMVplYcOfRWf2Be3td3sUa+1AaxmsYyANsA+znrGCBjXJNi4XAQlSoEfUxs/4kIQ==" }, "@next/eslint-plugin-next": { "version": "13.1.2", @@ -12473,82 +12410,58 @@ "source-map": "^0.7.0" } }, - "@next/swc-android-arm-eabi": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.2.tgz", - "integrity": "sha512-7mRz1owoGsbfIcdOJA3kk7KEwPZ+OvVT1z9DkR/yru4QdVLF69h/1SHy0vlUNQMxDRllabhxCfkoZCB34GOGAg==", - "optional": true - }, - "@next/swc-android-arm64": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.2.tgz", - "integrity": "sha512-mgjZ2eJSayovQm1LcE54BLSI4jjnnnLtq5GY5g+DdPuUiCT644gKtjZ/w2BQvuIecCqqBO+Ph9yzo/wUTq7NLg==", - "optional": true - }, "@next/swc-darwin-arm64": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.2.tgz", - "integrity": "sha512-RikoQqy109r2222UJlyGs4dZw2BibkfPqpeFdW5JEGv+L2PStlHID8DwyVYbmHfQ0VIBGvbf/NAUtFakAWlhwg==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.4.tgz", + "integrity": "sha512-Df8SHuXgF1p+aonBMcDPEsaahNo2TCwuie7VXED4FVyECvdXfRT9unapm54NssV9tF3OQFKBFOdlje4T43VO0w==", "optional": true }, "@next/swc-darwin-x64": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.2.tgz", - "integrity": "sha512-JbDZjaTvL8gyPC5TAH6OnD4jmXPkyUxRYPvu08ZmhT/XAFBb/Cso0BdXyDax/BPCG70mimP9d3hXNKNq+A0VtQ==", - "optional": true - }, - "@next/swc-freebsd-x64": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.2.tgz", - "integrity": "sha512-ax4j8VrdFQ/xc3W7Om0u1vnDxVApQHKsChBbAMynCrnycZmpbqK4MZu4ZkycT+mx2eccCiqZROpbzDbEdPosEw==", - "optional": true - }, - "@next/swc-linux-arm-gnueabihf": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.2.tgz", - "integrity": "sha512-NcRHTesnCxnUvSJa637PQJffBBkmqi5XS/xVWGY7dI6nyJ+pC96Oj7kd+mcjnFUQI5lHKbg39qBWKtOzbezc4w==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.4.tgz", + "integrity": "sha512-siPuUwO45PnNRMeZnSa8n/Lye5ZX93IJom9wQRB5DEOdFrw0JjOMu1GINB8jAEdwa7Vdyn1oJ2xGNaQpdQQ9Pw==", "optional": true }, "@next/swc-linux-arm64-gnu": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.2.tgz", - "integrity": "sha512-AxJdjocLtPrsBY4P2COSBIc3crT5bpjgGenNuINoensOlXhBkYM0aRDYZdydwXOhG+kN2ngUvfgitop9pa204w==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.4.tgz", + "integrity": "sha512-l/k/fvRP/zmB2jkFMfefmFkyZbDkYW0mRM/LB+tH5u9pB98WsHXC0WvDHlGCYp3CH/jlkJPL7gN8nkTQVrQ/2w==", "optional": true }, "@next/swc-linux-arm64-musl": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.2.tgz", - "integrity": "sha512-JmNimDkcCRq7P5zpkdqeaSZ69qKDntEPtyIaMNWqy5M0WUJxGim0Fs6Qzxayiyvuuh9Guxks4woQ/j/ZvX/c8Q==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.4.tgz", + "integrity": "sha512-YYGb7SlLkI+XqfQa8VPErljb7k9nUnhhRrVaOdfJNCaQnHBcvbT7cx/UjDQLdleJcfyg1Hkn5YSSIeVfjgmkTg==", "optional": true }, "@next/swc-linux-x64-gnu": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.2.tgz", - "integrity": "sha512-TsLsjZwUlgmvI42neTuIoD6K9RlXCUzqPtvIClgXxVO0um0DiZwK+M+0zX/uVXhMVphfPY2c5YeR1zFSIONY4A==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.4.tgz", + "integrity": "sha512-uE61vyUSClnCH18YHjA8tE1prr/PBFlBFhxBZis4XBRJoR+txAky5d7gGNUIbQ8sZZ7LVkSVgm/5Fc7mwXmRAg==", "optional": true }, "@next/swc-linux-x64-musl": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.2.tgz", - "integrity": "sha512-eSkyXgCXydEFPTkcncQOGepafedPte6JT/OofB9uvruucrrMVBagCASOuPxodWEMrlfEKSXVnExMKIlfmQMD7A==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.4.tgz", + "integrity": "sha512-qVEKFYML/GvJSy9CfYqAdUexA6M5AklYcQCW+8JECmkQHGoPxCf04iMh7CPR7wkHyWWK+XLt4Ja7hhsPJtSnhg==", "optional": true }, "@next/swc-win32-arm64-msvc": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.2.tgz", - "integrity": "sha512-DmXFaRTgt2KrV9dmRLifDJE+cYiutHVFIw5/C9BtnwXH39uf3YbPxeD98vNrtqqqZVVLXY/1ySaSIwzYnqeY9g==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.4.tgz", + "integrity": "sha512-mDSQfqxAlfpeZOLPxLymZkX0hYF3juN57W6vFHTvwKlnHfmh12Pt7hPIRLYIShk8uYRsKPtMTth/EzpwRI+u8w==", "optional": true }, "@next/swc-win32-ia32-msvc": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.2.tgz", - "integrity": "sha512-3+nBkuFs/wT+lmRVQNH5SyDT7I4vUlNPntosEaEP63FuYQdPLaxz0GvcR66MdFSFh2fsvazpe4wciOwVS4FItQ==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.4.tgz", + "integrity": "sha512-aoqAT2XIekIWoriwzOmGFAvTtVY5O7JjV21giozBTP5c6uZhpvTWRbmHXbmsjZqY4HnEZQRXWkSAppsIBweKqw==", "optional": true }, "@next/swc-win32-x64-msvc": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.2.tgz", - "integrity": "sha512-avsyveEvcvH42PvKjR4Pb8JlLttuGURr2H3ZhS2b85pHOiZ7yjH3rMUoGnNzuLMApyxYaCvd4MedPrLhnNhkog==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.4.tgz", + "integrity": "sha512-cyRvlAxwlddlqeB9xtPSfNSCRy8BOa4wtMo0IuI9P7Y0XT2qpDrpFKRyZ7kUngZis59mPVla5k8X1oOJ8RxDYg==", "optional": true }, "@nodelib/fs.scandir": { @@ -12672,9 +12585,9 @@ "dev": true }, "@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", "requires": { "tslib": "^2.4.0" } @@ -13389,6 +13302,14 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -14896,8 +14817,7 @@ "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "peer": true + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "global-modules": { "version": "2.0.0", @@ -17212,9 +17132,9 @@ "dev": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" }, "native-promise-only": { "version": "0.8.1", @@ -17257,28 +17177,26 @@ "peer": true }, "next": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/next/-/next-13.1.2.tgz", - "integrity": "sha512-Rdnnb2YH///w78FEOR/IQ6TXga+qpth4OqFSem48ng1PYYKr6XBsIk1XVaRcIGM3o6iiHnun0nJvkJHDf+ICyQ==", - "requires": { - "@next/env": "13.1.2", - "@next/swc-android-arm-eabi": "13.1.2", - "@next/swc-android-arm64": "13.1.2", - "@next/swc-darwin-arm64": "13.1.2", - "@next/swc-darwin-x64": "13.1.2", - "@next/swc-freebsd-x64": "13.1.2", - "@next/swc-linux-arm-gnueabihf": "13.1.2", - "@next/swc-linux-arm64-gnu": "13.1.2", - "@next/swc-linux-arm64-musl": "13.1.2", - "@next/swc-linux-x64-gnu": "13.1.2", - "@next/swc-linux-x64-musl": "13.1.2", - "@next/swc-win32-arm64-msvc": "13.1.2", - "@next/swc-win32-ia32-msvc": "13.1.2", - "@next/swc-win32-x64-msvc": "13.1.2", - "@swc/helpers": "0.4.14", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/next/-/next-13.5.4.tgz", + "integrity": "sha512-+93un5S779gho8y9ASQhb/bTkQF17FNQOtXLKAj3lsNgltEcF0C5PMLLncDmH+8X1EnJH1kbqAERa29nRXqhjA==", + "requires": { + "@next/env": "13.5.4", + "@next/swc-darwin-arm64": "13.5.4", + "@next/swc-darwin-x64": "13.5.4", + "@next/swc-linux-arm64-gnu": "13.5.4", + "@next/swc-linux-arm64-musl": "13.5.4", + "@next/swc-linux-x64-gnu": "13.5.4", + "@next/swc-linux-x64-musl": "13.5.4", + "@next/swc-win32-arm64-msvc": "13.5.4", + "@next/swc-win32-ia32-msvc": "13.5.4", + "@next/swc-win32-x64-msvc": "13.5.4", + "@swc/helpers": "0.5.2", + "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", - "postcss": "8.4.14", - "styled-jsx": "5.1.1" + "postcss": "8.4.31", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0" } }, "next-mdx-remote": { @@ -17665,11 +17583,11 @@ "dev": true }, "postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "requires": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -19043,6 +18961,11 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -19267,17 +19190,6 @@ "yargs-parser": "^20.2.3" } }, - "postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", - "dev": true, - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, "postcss-safe-parser": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", @@ -20121,7 +20033,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "peer": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" diff --git a/package.json b/package.json index 5d69a25abf..19f193b216 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "classnames": "2.3.2", "compare-versions": "6.0.0-rc.1", "gray-matter": "4.0.3", - "next": "13.1.2", + "next": "13.5.4", "next-mdx-remote": "4.2.0", "next-seo": "5.15.0", "prism-react-renderer": "1.3.5", From 4d6f8854f25aa7bf2bc03964946f100daac89848 Mon Sep 17 00:00:00 2001 From: sarthak-kumar-shailendra Date: Sat, 7 Oct 2023 17:10:59 +0530 Subject: [PATCH 188/264] update twitter icon to x Signed-off-by: sarthak-kumar-shailendra --- components/Icon.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/Icon.jsx b/components/Icon.jsx index eec7b631c2..b01454e0e9 100644 --- a/components/Icon.jsx +++ b/components/Icon.jsx @@ -129,9 +129,9 @@ function TwitterIcon() { xmlns="http://www.w3.org/2000/svg" width="100%" fill="currentColor" - viewBox="0 0 16 16" + viewBox="0 0 30 30" > - + ) } From cc19d081ce940994796d1eadd556979c39816e0e Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:27:27 +0200 Subject: [PATCH 189/264] cleanup installation section Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 1 + content/docs/concepts/webhook.md | 2 +- .../api-compatibility.md | 0 content/docs/contributing/crds.md | 2 +- content/docs/contributing/featuregates.md | 23 ++-- .../installation/configuring-components.md | 89 +++++++++++++ content/docs/installation/featureflags.md | 76 ----------- content/docs/installation/helm.md | 2 +- content/docs/installation/kubectl.md | 125 ++++++++++++++++-- content/docs/installation/other-tools.md | 23 ---- content/docs/installation/reinstall.md | 33 +++++ content/docs/installation/uninstall.md | 2 +- content/docs/installation/upgrade.md | 28 +--- content/docs/installation/verify.md | 122 ----------------- content/docs/manifest.json | 43 +++--- content/docs/reference/cmctl.md | 2 +- public/_redirects | 6 + 17 files changed, 282 insertions(+), 297 deletions(-) rename content/docs/{installation => contributing}/api-compatibility.md (100%) create mode 100644 content/docs/installation/configuring-components.md delete mode 100644 content/docs/installation/featureflags.md delete mode 100644 content/docs/installation/other-tools.md create mode 100644 content/docs/installation/reinstall.md delete mode 100644 content/docs/installation/verify.md diff --git a/.spelling b/.spelling index 6a2e900f0f..dfe9873015 100644 --- a/.spelling +++ b/.spelling @@ -261,6 +261,7 @@ Velero Venafi versioned WebhookConfiguration +ControllerConfiguration WIP YAML YAMLs diff --git a/content/docs/concepts/webhook.md b/content/docs/concepts/webhook.md index 1bcbbee769..884e92d7c8 100644 --- a/content/docs/concepts/webhook.md +++ b/content/docs/concepts/webhook.md @@ -73,7 +73,7 @@ For these reasons, after installing cert-manager and when performing post-instal you will need to check for temporary API configuration errors and retry. You could also add a post-installation check which performs `kubectl --dry-run` operations on the cert-manager API. -Or you could add a post-installation check which automatically retries the [Installation Verification](../installation/verify.md) steps until they succeed. +Or you could add a post-installation check which automatically retries the [Installation Verification](../installation/kubectl.md#verify) steps until they succeed. ### Other Webhook Problems diff --git a/content/docs/installation/api-compatibility.md b/content/docs/contributing/api-compatibility.md similarity index 100% rename from content/docs/installation/api-compatibility.md rename to content/docs/contributing/api-compatibility.md diff --git a/content/docs/contributing/crds.md b/content/docs/contributing/crds.md index 1cce203032..586a68d218 100644 --- a/content/docs/contributing/crds.md +++ b/content/docs/contributing/crds.md @@ -56,7 +56,7 @@ While cert-manager doesn't fully use Kubebuilder, CRDs can make use of special K ## Making Changes to APIs -Please see our [API compatibility promise](../installation/api-compatibility.md) for details on which types of changes to APIs are acceptable. +Please see our [API compatibility promise](../contributing/api-compatibility.md) for details on which types of changes to APIs are acceptable. Generally, the gist is that new fields can be added but that existing fields cannot be removed. diff --git a/content/docs/contributing/featuregates.md b/content/docs/contributing/featuregates.md index 4e6f332967..50e3e1bb8b 100644 --- a/content/docs/contributing/featuregates.md +++ b/content/docs/contributing/featuregates.md @@ -1,31 +1,34 @@ --- -Title: Implementing feature gates -description: 'cert-manager contributing guide: Implementing feature gates' +title: cert-manager feature gates +description: 'cert-manager contributing guide: Feature gates' --- -As of v1 release cert-manager is considered stable. We aim to follow Kubernetes API compatibility policy when making API changes, see [API compatibility](../installation/api-compatibility.md) to avoid breaking users' existing cert-manager installations. +As of v1 release cert-manager is considered stable. We aim to follow Kubernetes API compatibility policy when making API changes, see [API compatibility](../contributing/api-compatibility.md) to avoid breaking users' existing cert-manager installations. This means that as developers we are somewhat limited in regards to changing existing behavior, i.e renaming or removing API elements or changing their behavior. New functionality that is not yet stable[^1] can still be added, but it needs to be placed behind a feature gate. +## Enabling/ disabling feature gates + +Feature gates can be enabled or disabled using CLI flags or config files, more info can be found in [configuring components](../installation/configuring-components.md). + ## Feature gated API fields -Feature gated API fields are implemented using `--feature-gates` flags of cert-manager [webhook](../cli/webhook.md) and [controller](../cli/controller.md). +Feature gated API fields are implemented using `--feature-gates` flags of cert-manager [webhook](../cli/webhook.md), [cainjector](../cli/cainjector.md) and [controller](../cli/controller.md). A feature gated API field is always visible to the user (i.e when running `kubectl explain `), but is only functional if the relevant feature is explicitly enabled via feature flags for both the webhook and controller. If a user attempts to apply a resource with the feature gated field set to a non-nil value, but the feature gate is not enabled, the resource will get rejected by the webhook validation. This mechanism differs from [the one that Kubernetes uses for feature gated API field implementation](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md#new-field-in-existing-api-version) where the field will be simply set to nil if the feature gate is disabled. We chose to use webhook validation instead to make debugging easier for users who are attempting to use the feature gated field, but have forgotten to enable the feature gate. - ### Implementation - Implement the new field and document that it is feature gated and in order to use it the controller and webhook feature gates need enabling -- Add a new [webhook feature gate](https://github.com/cert-manager/cert-manager/blob/3a055cc2f56c1c2874807af4a8f84d0a1c46ccb4/internal/webhook/feature/features.go#L25-L39) for the field +- Add a new [webhook feature gate](https://github.com/cert-manager/cert-manager/blob/7c7e8f4ce6c1abba18025d3d00be368066801a63/internal/webhook/feature/features.go#L31-L64) for the field - Update webhook validation checks for the relevant resource kind to ensure that if the feature gated field is set, but the webhook feature gate is not enabled, the resource gets rejected -- Add a new [controller feature gate](https://github.com/cert-manager/cert-manager/blob/2417132b3cd017b5f0974006e03c2b8a540efe3f/internal/controller/feature/features.go#L26-L54) for the field +- Add a new [controller feature gate](https://github.com/cert-manager/cert-manager/blob/7c7e8f4ce6c1abba18025d3d00be368066801a63/internal/controller/feature/features.go#L32-L121) for the field - Ensure that any control loops that use the feature, check that the feature gate is actually enabled. (This is required to cover edge cases such as if the webhook runs a version of cert-manager where the feature is in GA whereas controller runs an older version where the feature is still in experimental state) -- Ensure that the feature gate is added to cert-manager installation scripts for CI and local tests in [make](https://github.com/cert-manager/cert-manager/blob/134398e939bb2b1401697eaf589405ad469cd609/make/e2e-setup.mk#L165) and [bazel](https://github.com/cert-manager/cert-manager/blob/fd747b42b9ab4b6409b61b7946e8dc14d532e950/devel/addon/certmanager/install.sh#L26) scripts +- Ensure that the feature gate is added to cert-manager installation scripts for CI and local tests in [make](https://github.com/cert-manager/cert-manager/blob/7c7e8f4ce6c1abba18025d3d00be368066801a63/make/e2e-setup.mk#L197) and [bash](https://github.com/cert-manager/cert-manager/blob/7c7e8f4ce6c1abba18025d3d00be368066801a63/make/e2e.sh#L80) scripts - Default cert-manger e2e CI tests run with all feature gates for all components enabled. There is an additional optional e2e test that runs with all feature gates disabled. You can trigger that for your PR with `/test pull-cert-manager-e2e-feature-gates-disabled` to verify that all works as expected both with and without the new feature gate. ### Potential issues @@ -36,9 +39,7 @@ This mechanism differs from [the one that Kubernetes uses for feature gated API ### References -- cert-manager's [API compatibility promise](../installation/api-compatibility.md) - -- An example implementation of an alpha field is [`AdditionalOutputFormats` field on `Certificate` spec](https://github.com/cert-manager/cert-manager/blob/dbad3d98f3d7d85cadb4bd2c2493faf8b666b313/internal/apis/certmanager/types_certificate.go#L169-L174) +- cert-manager's [API compatibility promise](../contributing/api-compatibility.md) - [Kubernetes definition of feature stages](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages) diff --git a/content/docs/installation/configuring-components.md b/content/docs/installation/configuring-components.md new file mode 100644 index 0000000000..061c982b74 --- /dev/null +++ b/content/docs/installation/configuring-components.md @@ -0,0 +1,89 @@ +--- +title: cert-manager component configuration +description: 'Configure cert-manager components using CLI flags or a configuration file' +--- + +To configure the cert-manager components, you can use CLI flags or a configuration file. +The CLI flags take precedence over the configuration file. + +## CLI flags + +An overview of the available CLI flags for each component can be found on the following pages: +- cert-manager controller: [controller CLI flags](../cli/controller.md) +- cert-manager webhook: [webhook CLI flags](../cli/webhook.md) +- cert-manager cainjector: [cainjector CLI flags](../cli/cainjector.md) +- cert-manager acmesolver: [acmesolver CLI flags](../cli/acmesolver.md) +- cert-manager cmctl: [cmctl CLI flags](../cli/cmctl.md) + +When using the Helm chart, the CLI flags can be specified in the `controller.extraArgs`, `webhook.extraArgs`, `cainjector.extraArgs` and `acmesolver.extraArgs` values. + +## Configuration file + +The configuration file is a YAML file that contains the configuration for the cert-manager components. +The configuration file can be specified using the `--config` CLI flag. When using the Helm chart, the +configuration file can be specified in the `config` and `webhook.config` values. + +### Controller configuration file + +The webhook configuration API documentation can be found on the [ControllerConfiguration](../reference/api-docs.md#controller.config.cert-manager.io/v1alpha1.ControllerConfiguration) page. + +This is an example configuration file for the controller: +```yaml +apiVersion: controller.config.cert-manager.io/v1alpha1 +kind: ControllerConfiguration + +logging: + verbosity: 2 + format: text + +leaderElectionConfig: + namespace: my-namespace + +kubernetesAPIQPS: 10 +kubernetesAPIBurst: 50 + +numberOfConcurrentWorkers: 200 + +featureGates: + additionalCertificateOutputFormats: true + experimentalCertificateSigningRequestControllers: true + experimentalGatewayAPISupport: true + serverSideApply: true + literalCertificateSubject: true + useCertificateRequestBasicConstraints: true +``` + +### Webhook configuration file + +The webhook configuration API documentation can be found on the [WebhookConfiguration](../reference/api-docs.md#webhook.config.cert-manager.io/v1alpha1.WebhookConfiguration) page. + +This is an example configuration file for the webhook: +```yaml +apiVersion: webhook.config.cert-manager.io/v1alpha1 +kind: WebhookConfiguration + +logging: + verbosity: 2 + format: text + +securePort: 6443 +healthzPort: 6080 + +featureGates: + additionalCertificateOutputFormats: true + literalCertificateSubject: true +``` + +## Feature gates + +Feature gates can be used to enable or disable experimental features in cert-manager. + +There are 2 levels of feature gates (more details in [Kubernetes definition of feature stages](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages)): +- **Alpha:** feature is not yet stable and might be removed or changed in the future. Alpha features are disabled by default and need to be explicitly enabled by the user (to test the feature). +- **Beta:** feature is almost stable but might still change in the future. Beta features are enabled by default and can be disabled by the user (if any issues are encountered). + +Each cert-manager component has its own set of feature gates. They can be enabled/ disabled using the `--feature-gates` flag or the `featureGates` value in the config file. The available feature gates for each component can be found on the following pages: + +- cert-manager controller: [controller feature gates](https://github.com/cert-manager/cert-manager/blob/master/internal/controller/feature/features.go) +- cert-manager webhook: [webhook feature gates](https://github.com/cert-manager/cert-manager/blob/master/internal/webhook/feature/features.go) +- cert-manager cainjector: [cainjector feature gates](https://github.com/cert-manager/cert-manager/blob/master/internal/cainjector/feature/features.go) diff --git a/content/docs/installation/featureflags.md b/content/docs/installation/featureflags.md deleted file mode 100644 index a3381d1f2a..0000000000 --- a/content/docs/installation/featureflags.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Feature flags -description: Using feature gated functionality ---- - -New cert-manager features and functionality are often initially implemented behind a feature gate. This is so as to not break users with functionality that has not yet been tested in production as well as to give us a chance to remove or modify API fields and functionality following user feedback. - -We have alpha and beta features. We do not aim to keep any of the features in alpha or beta stage indefinitely. Feature gating should only be used for functionality that can eventually be turned on by default for all users (or, if it is opt-in, can be safely toggled on by any user with a supported cert-manager installation). - -A feature gate can be toggled on/off using `--feature-gates` flags on cert-manager controller. For feature gated functionality that comes with new API fields there is also a corresponding feature gate on webhook that also needs to be enabled using a `--feature-gates` flag if you want to use it. - -**Alpha** - -All alpha features are off by default. We retain the right to change or remove -alpha features without warning. An API field that is part of an alpha feature -and requires a webhook feature flag to be used also can also be removed from the -API without warning. An alpha feature might not work for all cert-manager's -supported Kubernetes versions. If you want to disable a previously enabled alpha -feature gate, you should make sure that you have updated any resources that have API -fields set that are only valid if the feature gate is enabled, else the resources -will end up in an invalid state. - -**Beta** - -All beta features are off by default. Beta features will not be removed, but may -be changed. If the feature gets changed in incompatible ways, we will provide -migration instructions. A beta feature will work with all cert-manager's -supported Kubernetes versions. If you want to disable a previously enabled beta -feature gate, you should make sure that you have updated any resources that have API -fields set that are only valid if the beta feature gate is on, else the resources -will end up in invalid state. - -**GA** - -A feature that is GA is on by default and cannot be disabled (unless it's opt in and toggled on/off by another mechanism, such as a flag). -With regards to API fields and their functionality, we keep Kubernetes API compatibility promise, see [API compatibility](./api-compatibility.md). -The feature flag for a GA feature might be left in place to avoid breaking folks, but will be non-functional. - -## Graduation - -The graduation criteria can be different for each feature. -Generally, we find user feedback most valuable when determining if a feature is sufficiently mature to graduate. If you are using an alpha or beta feature and would like to see it graduate, it would be great if you could give us some feedback about how you use it and whether you find the API useful to initiate graduation process. Feel free to [open a GitHub issue](https://github.com/cert-manager/cert-manager/issues/new/choose) or [join one of our meetings](../contributing/#meetings) to talk about this. - -## List of current feature gates - -### Alpha - -See `--feature-gates` flags on cert-manager controller and webhook to enable any of these features. - -- `AdditionalCertificateOutputFormats`. Added in cert-manager 1.7.0. Allows to specify additional formats in which cert-manager will store issued certificates and keys. See [release note](../releases/release-notes/release-notes-1.7.md#additional-certificate-output-formats). Requires the feature to be enabled on both cert-manager controller and webhook - -- `ExperimentalCertificateSigningRequestControllers`. Added in cert-manager - 1.4.0. Allows to use Kubernetes - [CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) - resources with cert-manager. See [release notes](../releases/release-notes/release-notes-1.4.md#experimental-support-for-kubernetes-certificatesigningrequests) - -- `ExperimentalGatewayAPISupport`. Added in cert-manager 1.5.0. Allows to use cert-manager to automatically issue certificates for `Gateway` resources as well as use `Gateway`s and `HTTPRoute`s to solve ACME HTTP-01 challenges. See [Securing Gateway resources](../usage/gateway.md) - -- `LiteralCertificateSubject`. Added in cert-manager 1.9.0. Allows to specify certificate subject in a form that can be used to define a location in LDAP directory tree. See [release notes](../releases/release-notes/release-notes-1.9.md#literal-certificate-subjects) - -- `ServerSideApply`. Added in cert-manager 1.8.0. If this feature is enabled, cert-manager uses [Server side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) when creating or updating API resources. This will speed cert-manager operations and prevent the resource version conflict errors. See [release notes](../releases/release-notes/release-notes-1.8.md#server-side-apply) - -- `UseCertificateRequestBasicConstraints`. Added in cert-manager 1.12.0. Makes cert-manager add a basic constraints section to certificate signing requests with the CA constraint set to the correct value. See [`cert-manager#5552`](https://github.com/cert-manager/cert-manager/pull/5552) - -- `ValidateCAA`. Added in cert-manager 0.7.2. CAA checking when issuing a certificate. - - -### Beta - -These features are enabled by default. See `--feature-gates` flags on cert-manager controller and webhook to disable any of these features. - -- `StableCertificateRequestName`. Alpha in v1.10 and Beta in v1.13. Enables generation of `CertificateRequest` resources with a fixed name. See [`cert-manager#5487`](https://github.com/cert-manager/cert-manager/pull/5487) - -- `SecretsFilteredCaching`. Alpha in v1.12 and Beta in v1.13. Reduces controller's memory consumption by filtering which Secrets are cached in full using `controller.cert-manager.io/fao` label. By default all Certificate Secrets are labelled with `controller.cert-manager.io/fao` label. Users can also label other Secrets, such as issuer credentials Secrets that they know cert-manager will need access to to speed up issuance. See [`20221205-memory-management.md`](https://github.com/cert-manager/cert-manager/blob/master/design/20221205-memory-management.md) - -- `DisallowInsecureCSRUsageDefinition`. Beta in v1.13. Prevents the webhook from allowing CertificateRequest's usages to be only defined in the CSR, while leaving the usages field empty. diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index 015c9aa875..02a324bacf 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -89,7 +89,7 @@ helm install \ --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter ``` -Once you have deployed cert-manager, you can [verify](./verify.md) the installation. +Once you have deployed cert-manager, you can [verify](./kubectl.md#verify) the installation. ### Installing cert-manager as subchart diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index 5f4caf8d7c..405ece3db8 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -13,7 +13,9 @@ Learn how to install cert-manager using kubectl and static manifests. ## Steps -All resources (the [`CustomResourceDefinitions`](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions) and the cert-manager, cainjector and webhook components) +### 1. Install from the cert-manager release manifest + +All resources (the CustomResourceDefinitions and the cert-manager, cainjector and webhook components) are included in a single YAML manifest file: Install all cert-manager components: @@ -26,23 +28,124 @@ By default, cert-manager will be installed into the `cert-manager` namespace. It is possible to run cert-manager in a different namespace, although you'll need to make modifications to the deployment manifests. -Once you have deployed cert-manager, you can [verify the installation](./verify.md). +Once you've installed cert-manager, you can verify it is deployed correctly by +checking the `cert-manager` namespace for running pods: + +```bash +$ kubectl get pods --namespace cert-manager + +NAME READY STATUS RESTARTS AGE +cert-manager-5c6866597-zw7kh 1/1 Running 0 2m +cert-manager-cainjector-577f6d9fd7-tr77l 1/1 Running 0 2m +cert-manager-webhook-787858fcdb-nlzsq 1/1 Running 0 2m +``` + +You should see the `cert-manager`, `cert-manager-cainjector`, and +`cert-manager-webhook` pods in a `Running` state. The webhook might take a +little longer to successfully provision than the others. + +If you experience problems, first check the [FAQ](../faq/README.md). + +### 2. (optional) Wait for cert-manager webhook to be ready + +The webhook component can take some time to start, and make the Kubernetes API server trust the webhook's certificate. + +First, make sure that [cmctl is installed](../reference/cmctl.md#installation). + +cmctl performs a dry-run certificate creation check against the Kubernetes cluster. +If successful, the message `The cert-manager API is ready` is displayed. + +```bash +$ cmctl check api +The cert-manager API is ready +``` + +The command can also be used to wait for the check to be successful. +Here is an output example of running the command at the same time that cert-manager is being installed: -## Permissions Errors on Google Kubernetes Engine +```bash +$ cmctl check api --wait=2m +Not ready: the cert-manager CRDs are not yet installed on the Kubernetes API server +Not ready: the cert-manager CRDs are not yet installed on the Kubernetes API server +Not ready: the cert-manager webhook deployment is not ready yet +Not ready: the cert-manager webhook deployment is not ready yet +Not ready: the cert-manager webhook deployment is not ready yet +Not ready: the cert-manager webhook deployment is not ready yet +The cert-manager API is ready +``` + +
              +### 2. (optional) End-to-end verify the installation + +Best way to fully verify the installation is to issue a test certificate. For this, we will create a self-signed issuer and a certificate resource in a test namespace. -When running on GKE (Google Kubernetes Engine), you might encounter a 'permission denied' error when creating some -of the required resources. This is a nuance of the way GKE handles RBAC and IAM permissions, -and as such you might need to elevate your own privileges to that of a "cluster-admin" **before** -running `kubectl apply`. -If you have already run `kubectl apply`, you should run it again after elevating your permissions: +```bash +$ cat < test-resources.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: cert-manager-test +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: test-selfsigned + namespace: cert-manager-test +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: selfsigned-cert + namespace: cert-manager-test +spec: + dnsNames: + - example.com + secretName: selfsigned-cert-tls + issuerRef: + name: test-selfsigned +EOF +``` +Create the test resources. ```bash -kubectl create clusterrolebinding cluster-admin-binding \ - --clusterrole=cluster-admin \ - --user=$(gcloud config get-value core/account) +$ kubectl apply -f test-resources.yaml ``` +Check the status of the newly created certificate. You may need to wait a few +seconds before cert-manager processes the certificate request. +```bash +$ kubectl describe certificate -n cert-manager-test + +... +Spec: + Common Name: example.com + Issuer Ref: + Name: test-selfsigned + Secret Name: selfsigned-cert-tls +Status: + Conditions: + Last Transition Time: 2019-01-29T17:34:30Z + Message: Certificate is up to date and has not expired + Reason: Ready + Status: True + Type: Ready + Not After: 2019-04-29T17:34:29Z +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal CertIssued 4s cert-manager Certificate issued successfully +``` + +Clean up the test resources. +```bash +$ kubectl delete -f test-resources.yaml +``` + +If all the above steps have completed without error, you're good to go! + ## Uninstalling > **Warning**: To uninstall cert-manager you should always use the same process for > installing but in reverse. Deviating from the following process whether diff --git a/content/docs/installation/other-tools.md b/content/docs/installation/other-tools.md deleted file mode 100644 index 6d7b0b7044..0000000000 --- a/content/docs/installation/other-tools.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Alternative installation methods -description: 'cert-manager installation: Other tools' ---- - -### kubeprod - -[Bitnami Kubernetes Production -Runtime](https://github.com/bitnami/kube-prod-runtime) (`BKPR`, `kubeprod`) is a -curated collection of the services you would need to deploy on top of your -Kubernetes cluster to enable logging, monitoring, certificate management, -automatic discovery of Kubernetes resources via public DNS servers and other -common infrastructure needs. - -It depends on `cert-manager` for certificate management, and it is [regularly -tested](https://github.com/bitnami/kube-prod-runtime/blob/master/Jenkinsfile) so -the components are known to work together for GKE, AKS, and EKS clusters. For -its ingress stack it creates a DNS entry in the configured DNS zone and requests -a TLS certificate from the Let's Encrypt staging server. - -BKPR can be deployed using the `kubeprod install` command, which will deploy -`cert-manager` as part of it. Details available in the [BKPR installation -guide](https://github.com/bitnami/kube-prod-runtime/blob/master/docs/install.md). \ No newline at end of file diff --git a/content/docs/installation/reinstall.md b/content/docs/installation/reinstall.md new file mode 100644 index 0000000000..9c544b3723 --- /dev/null +++ b/content/docs/installation/reinstall.md @@ -0,0 +1,33 @@ +--- +title: Reinstalling cert-manager +description: 'cert-manager installation: Reinstalling cert-manager overview' +--- + +In some cases there may be a need to do a full uninstall and re-install of +cert-manager. An example could be when a very old cert-manager version needs to +be brought up to date and it isn't feasible to upgrade one minor version at a +time, which is our default recommended upgrade strategy. + +If cert-manager `CustomResourceDefinition`s are also uninstalled, this will mean +loss of associated cert-manager custom resources such as `Certificate`s. The +main concern associated with this is application downtime and unnecessary +certificate reissuance, that could happen if `Secret`s with the X.509 +certificates get deleted. You can use [`--enable-certificate-owner-ref` +flag](https://cert-manager.io/docs/cli/controller/) +on the cert-manager controller to configure whether the `Secret`s should be deleted. +If this flag is set to true, each `Secret` will have an owner reference to the +`Certificate` for which it was created and when the `Certificate` is deleted, +the `Secret` will be garbage collected. The default value for this flag is +false. If the `Certificate`s get deleted and re-applied, but the `Secret`s remain +in the cluster, the newly applied `Certificate`s should be able to pick up the +same `Secret`s and should not unnecessarily reissue the X.509 certs. + +When uninstalling and re-installing in order to upgrade, you should still read +through the release notes for each skipped version. + +Some things to look out for when considering uninstalling and re-installing +cert-manager _including the CRDs_: + +- Is `--enable-certificate-owner-ref` flag currently set to true or could it have been set to true at some point previously? Due to an earlier bug, the owner reference that gets added to `Secret`s is _not_ removed when the value of `--enable-certificate-owner-ref` is changed from true to false, see [`cert-manager#4788`](https://github.com/cert-manager/cert-manager/issues/4788) +- Are there currently any certificate issuances in progress? If so, with the custom resources deleted, the progress will be lost. This could potentially cause duplicated issuances. +- Is there a need to convert cert-manager custom resource manifests to v1 API? You can use [`cmctl convert` command](../reference/cmctl.md#convert) to do that. diff --git a/content/docs/installation/uninstall.md b/content/docs/installation/uninstall.md index 404fb4b8e6..3ffc30772c 100644 --- a/content/docs/installation/uninstall.md +++ b/content/docs/installation/uninstall.md @@ -1,5 +1,5 @@ --- -title: Uninstall +title: Uninstalling cert-manager description: 'cert-manager installation: Uninstalling cert-manager' --- diff --git a/content/docs/installation/upgrade.md b/content/docs/installation/upgrade.md index bff4e6cba6..cc02cd1056 100644 --- a/content/docs/installation/upgrade.md +++ b/content/docs/installation/upgrade.md @@ -1,5 +1,5 @@ --- -title: Upgrading +title: Upgrading cert-manager description: 'cert-manager installation: Upgrading cert-manager overview' --- @@ -87,7 +87,7 @@ number you want to install: kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download//cert-manager.yaml ``` -Once you have deployed the new version of cert-manager, you can [verify](verify.md) the installation. +Once you have deployed the new version of cert-manager, you can [verify](kubectl.md#verify) the installation. ## Reinstalling cert-manager @@ -96,26 +96,4 @@ cert-manager. An example could be when a very old cert-manager version needs to be brought up to date and it isn't feasible to upgrade one minor version at a time, which is our default recommended upgrade strategy. -If cert-manager `CustomResourceDefinition`s are also uninstalled, this will mean -loss of associated cert-manager custom resources such as `Certificate`s. The -main concern associated with this is application downtime and unnecessary -certificate reissuance, that could happen if `Secret`s with the X.509 -certificates get deleted. You can use [`--enable-certificate-owner-ref` -flag](https://cert-manager.io/docs/cli/controller/) -on the cert-manager controller to configure whether the `Secret`s should be deleted. -If this flag is set to true, each `Secret` will have an owner reference to the -`Certificate` for which it was created and when the `Certificate` is deleted, -the `Secret` will be garbage collected. The default value for this flag is -false. If the `Certificate`s get deleted and re-applied, but the `Secret`s remain -in the cluster, the newly applied `Certificate`s should be able to pick up the -same `Secret`s and should not unnecessarily reissue the X.509 certs. - -When uninstalling and re-installing in order to upgrade, you should still read -through the release notes for each skipped version. - -Some things to look out for when considering uninstalling and re-installing -cert-manager _including the CRDs_: - -- Is `--enable-certificate-owner-ref` flag currently set to true or could it have been set to true at some point previously? Due to an earlier bug, the owner reference that gets added to `Secret`s is _not_ removed when the value of `--enable-certificate-owner-ref` is changed from true to false, see [`cert-manager#4788`](https://github.com/cert-manager/cert-manager/issues/4788) -- Are there currently any certificate issuances in progress? If so, with the custom resources deleted, the progress will be lost. This could potentially cause duplicated issuances. -- Is there a need to convert cert-manager custom resource manifests to v1 API? You can use [`cmctl convert` command](../reference/cmctl.md#convert) to do that. +See [Reinstalling cert-manager](reinstall.md) for a full guide on how to do this without any issues. diff --git a/content/docs/installation/verify.md b/content/docs/installation/verify.md deleted file mode 100644 index 4a169f1c95..0000000000 --- a/content/docs/installation/verify.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -title: Verifying the Installation -description: 'cert-manager installation: Verifying an upgrade was successful' ---- - -## Check cert-manager API - -First, make sure that [cmctl is installed](../reference/cmctl.md#installation). - -cmctl performs a dry-run certificate creation check against the Kubernetes cluster. -If successful, the message `The cert-manager API is ready` is displayed. - -```bash -$ cmctl check api -The cert-manager API is ready -``` - -The command can also be used to wait for the check to be successful. -Here is an output example of running the command at the same time that cert-manager is being installed: - -```bash -$ cmctl check api --wait=2m -Not ready: the cert-manager CRDs are not yet installed on the Kubernetes API server -Not ready: the cert-manager CRDs are not yet installed on the Kubernetes API server -Not ready: the cert-manager webhook deployment is not ready yet -Not ready: the cert-manager webhook deployment is not ready yet -Not ready: the cert-manager webhook deployment is not ready yet -Not ready: the cert-manager webhook deployment is not ready yet -The cert-manager API is ready -``` - -## Manual verification - -Once you've installed cert-manager, you can verify it is deployed correctly by -checking the `cert-manager` namespace for running pods: - -```bash -$ kubectl get pods --namespace cert-manager - -NAME READY STATUS RESTARTS AGE -cert-manager-5c6866597-zw7kh 1/1 Running 0 2m -cert-manager-cainjector-577f6d9fd7-tr77l 1/1 Running 0 2m -cert-manager-webhook-787858fcdb-nlzsq 1/1 Running 0 2m -``` - -You should see the `cert-manager`, `cert-manager-cainjector`, and -`cert-manager-webhook` pods in a `Running` state. The webhook might take a -little longer to successfully provision than the others. - -If you experience problems, first check the [FAQ](../faq/README.md). - -Create an `Issuer` to test the webhook works okay. -```bash -$ cat < test-resources.yaml -apiVersion: v1 -kind: Namespace -metadata: - name: cert-manager-test ---- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - name: test-selfsigned - namespace: cert-manager-test -spec: - selfSigned: {} ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: selfsigned-cert - namespace: cert-manager-test -spec: - dnsNames: - - example.com - secretName: selfsigned-cert-tls - issuerRef: - name: test-selfsigned -EOF -``` - -Create the test resources. -```bash -$ kubectl apply -f test-resources.yaml -``` - -Check the status of the newly created certificate. You may need to wait a few -seconds before cert-manager processes the certificate request. -```bash -$ kubectl describe certificate -n cert-manager-test - -... -Spec: - Common Name: example.com - Issuer Ref: - Name: test-selfsigned - Secret Name: selfsigned-cert-tls -Status: - Conditions: - Last Transition Time: 2019-01-29T17:34:30Z - Message: Certificate is up to date and has not expired - Reason: Ready - Status: True - Type: Ready - Not After: 2019-04-29T17:34:29Z -Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal CertIssued 4s cert-manager Certificate issued successfully -``` - -Clean up the test resources. -```bash -$ kubectl delete -f test-resources.yaml -``` - -If all the above steps have completed without error, you're good to go! - -## Community-maintained tool - -Alternatively, to automatically check if cert-manager is correctly configured, -you can run the community-maintained [cert-manager-verifier](https://github.com/alenkacz/cert-manager-verifier) tool. diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 4cacaff146..75afe171c4 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -266,7 +266,6 @@ } ] }, - { "title": "0. Installation", "routes": [ @@ -274,10 +273,6 @@ "title": "Introduction", "path": "/docs/installation/README.md" }, - { - "title": "Cloud Compatibility", - "path": "/docs/installation/compatibility.md" - }, { "title": "a. kubectl apply", "path": "/docs/installation/kubectl.md" @@ -291,32 +286,20 @@ "path": "/docs/installation/operator-lifecycle-manager.md" }, { - "title": "d. Other tools", - "path": "/docs/installation/other-tools.md" - }, - { - "title": "Feature flags", - "path": "/docs/installation/featureflags.md" - }, - { - "title": "Best Practice", - "path": "/docs/installation/best-practice.md" - }, - { - "title": "Verify installation", - "path": "/docs/installation/verify.md" + "title": "Configuring Components", + "path": "/docs/installation/configuring-components.md" }, { "title": "Upgrade", "path": "/docs/installation/upgrade.md" }, { - "title": "Uninstall", - "path": "/docs/installation/uninstall.md" + "title": "Reinstall", + "path": "/docs/installation/reinstall.md" }, { - "title": "API compatibility", - "path": "/docs/installation/api-compatibility.md" + "title": "Uninstall", + "path": "/docs/installation/uninstall.md" }, { "title": "Signature Verification", @@ -588,6 +571,10 @@ { "title": "DevOps Tips", "routes": [ + { + "title": "Installing on a Cloud Provider", + "path": "/docs/installation/compatibility.md" + }, { "title": "Prometheus Metrics", "path": "/docs/devops-tips/prometheus-metrics.md" @@ -599,6 +586,10 @@ { "title": "Syncing Secrets Across Namespaces", "path": "/docs/devops-tips/syncing-secrets-across-namespaces.md" + }, + { + "title": "Best Practice Installation Options", + "path": "/docs/installation/best-practice.md" } ] }, @@ -675,7 +666,7 @@ "path": "/docs/contributing/kind.md" }, { - "title": "Implementing Feature Gates", + "title": "Feature gates", "path": "/docs/contributing/featuregates.md" }, { @@ -716,6 +707,10 @@ "title": "Signing Keys", "path": "/docs/contributing/signing-keys.md" }, + { + "title": "API compatibility", + "path": "/docs/contributing/api-compatibility.md" + }, { "title": "Importing cert-manager in Go", "path": "/docs/contributing/importing.md" diff --git a/content/docs/reference/cmctl.md b/content/docs/reference/cmctl.md index 0b3bfc957b..3b79561427 100644 --- a/content/docs/reference/cmctl.md +++ b/content/docs/reference/cmctl.md @@ -282,7 +282,7 @@ cmctl x install \ ``` You can find [a full list of the install parameters on cert-manager's ArtifactHub page](https://artifacthub.io/packages/helm/cert-manager/cert-manager#configuration). These are the same parameters that are available when using the Helm chart. -Once you have deployed cert-manager, you can [verify](../installation/verify.md) the installation. +Once you have deployed cert-manager, you can [verify](../installation/kubectl.md#verify) the installation. The CLI also allows the user to output the templated manifest to `stdout`, instead of installing the manifest on the cluster. diff --git a/public/_redirects b/public/_redirects index aedf467951..8a893e2de4 100644 --- a/public/_redirects +++ b/public/_redirects @@ -205,6 +205,12 @@ https://docs.cert-manager.io/* https://cert-manager.io/docs/:splat 302! /docs/projects/approver-policy/ /docs/policy/approval/approver-policy/ 301! /docs/projects/approver-policy/api-reference/ /docs/policy/approval/approver-policy/api-reference/ 301! +# Installation section cleanup +/docs/installation/other-tools/ /docs/installation/helm/ 301! +/docs/installation/verify/ /docs/installation/kubectl/ 301! +/docs/installation/featureflags/ /docs/installation/configuring-components/ 301! +/docs/installation/api-compatibility/ /docs/contributing/api-compatibility/ 301! + # Moved the "upgrading" and "release-notes" pages to the release section /docs/installation/upgrading/* /docs/releases/upgrading/:splat 301! /docs/release-notes/* /docs/releases/release-notes/:splat 301! From 5def5f72d33543db8eaae81affdf8098da0c189e Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:55:08 +0200 Subject: [PATCH 190/264] re-organise the usage section & move missing topics to this section Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/manifest.json | 16 ++++++++-------- content/docs/usage/certificate.md | 2 +- .../{concepts => usage}/certificaterequest.md | 2 +- content/docs/usage/gateway.md | 2 +- content/docs/usage/ingress.md | 2 +- content/docs/usage/kube-csr.md | 2 +- public/_redirects | 3 +++ 7 files changed, 16 insertions(+), 13 deletions(-) rename content/docs/{concepts => usage}/certificaterequest.md (99%) diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 75afe171c4..005a1d6d20 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -411,19 +411,23 @@ "path": "/docs/usage/README.md" }, { - "title": "Certificate Resources", + "title": "⚓ Certificate", "path": "/docs/usage/certificate.md" }, { - "title": "Securing Ingress Resources", + "title": "⚓ CertificateRequest", + "path": "/docs/usage/certificaterequest.md" + }, + { + "title": "☸️ Ingress", "path": "/docs/usage/ingress.md" }, { - "title": "Securing Gateway Resources", + "title": "☸️ Gateway", "path": "/docs/usage/gateway.md" }, { - "title": "Kubernetes CertificateSigningRequests", + "title": "☸️ CertificateSigningRequests", "path": "/docs/usage/kube-csr.md" }, { @@ -782,10 +786,6 @@ "title": "Certificate", "path": "/docs/concepts/certificate.md" }, - { - "title": "CertificateRequest", - "path": "/docs/concepts/certificaterequest.md" - }, { "title": "ACME Orders and Challenges", "path": "/docs/concepts/acme-orders-challenges.md" diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index a11dcd08b3..ca29de1e47 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -1,5 +1,5 @@ --- -title: Certificate Resources +title: cert-manager.io Certificate description: 'cert-manager usage: Certificates' --- diff --git a/content/docs/concepts/certificaterequest.md b/content/docs/usage/certificaterequest.md similarity index 99% rename from content/docs/concepts/certificaterequest.md rename to content/docs/usage/certificaterequest.md index 5dbc1ca546..6f1d092948 100644 --- a/content/docs/concepts/certificaterequest.md +++ b/content/docs/usage/certificaterequest.md @@ -1,5 +1,5 @@ --- -title: CertificateRequest +title: cert-manager.io CertificateRequest description: 'cert-manager core concepts: CertificateRequests' --- diff --git a/content/docs/usage/gateway.md b/content/docs/usage/gateway.md index 45f8fe62b7..ae852ef6b7 100644 --- a/content/docs/usage/gateway.md +++ b/content/docs/usage/gateway.md @@ -1,5 +1,5 @@ --- -title: Securing gateway.networking.k8s.io Gateway Resources +title: Annotated gateway.networking.k8s.io Gateway description: 'cert-manager usage: Kubernetes Gateways' --- diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index 26ff55751e..af82abbd0a 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -1,5 +1,5 @@ --- -title: Securing Ingress Resources +title: Annotated networking.k8s.io Ingress description: 'cert-manager usage: Kubernetes Ingress' --- diff --git a/content/docs/usage/kube-csr.md b/content/docs/usage/kube-csr.md index f6abb93f74..a4ff6a1a53 100644 --- a/content/docs/usage/kube-csr.md +++ b/content/docs/usage/kube-csr.md @@ -1,5 +1,5 @@ --- -title: Kubernetes CertificateSigningRequests +title: certificates.k8s.io CertificateSigningRequest description: 'cert-manager usage: Kubernetes CertificateSigningRequest resources' --- diff --git a/public/_redirects b/public/_redirects index 8a893e2de4..2d80cbc1ad 100644 --- a/public/_redirects +++ b/public/_redirects @@ -215,3 +215,6 @@ https://docs.cert-manager.io/* https://cert-manager.io/docs/:splat 302! /docs/installation/upgrading/* /docs/releases/upgrading/:splat 301! /docs/release-notes/* /docs/releases/release-notes/:splat 301! /docs/installation/supported-releases/ /docs/releases/ 301! + +# Moved the concept pages into the main website +/docs/concepts/certificaterequest/ /docs/usage/certificaterequest/ 301! From d9a71b1d730b331da9a40daee599d667e8aee4c3 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:56:49 +0200 Subject: [PATCH 191/264] fix broken links Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/concepts/acme-orders-challenges.md | 2 +- content/docs/contributing/external-issuers.md | 6 +++--- content/docs/policy/approval/README.md | 4 ++-- content/docs/policy/approval/approver-policy/README.md | 6 +++--- content/docs/reference/cmctl.md | 2 +- content/docs/usage/certificaterequest.md | 2 +- content/docs/usage/csi-driver-spiffe.md | 10 +++++----- content/docs/usage/csi-driver.md | 2 +- content/docs/usage/kube-csr.md | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/content/docs/concepts/acme-orders-challenges.md b/content/docs/concepts/acme-orders-challenges.md index 80c31606e4..e7c67a4a6e 100644 --- a/content/docs/concepts/acme-orders-challenges.md +++ b/content/docs/concepts/acme-orders-challenges.md @@ -20,7 +20,7 @@ In order to complete these challenges, cert-manager introduces two validation can be found on the Let's Encrypt website [here](https://letsencrypt.org/how-it-works/). An order represents a single certificate request which will be created automatically once a new -[`CertificateRequest`](./certificaterequest.md) resource referencing an ACME +[`CertificateRequest`](../usage/certificaterequest.md) resource referencing an ACME issuer has been created. `CertificateRequest` resources are created automatically by cert-manager once a [`Certificate`](./certificate.md) resource is created, has its specification changed, or needs renewal. diff --git a/content/docs/contributing/external-issuers.md b/content/docs/contributing/external-issuers.md index 4d18572fb3..f4408e1c15 100644 --- a/content/docs/contributing/external-issuers.md +++ b/content/docs/contributing/external-issuers.md @@ -49,13 +49,13 @@ on how to write an external issuer using Kubebuilder and controller-runtime. ## Approval Before signing a certificate, Issuers **must** also ensure that the `CertificateRequest` is -[`Approved`](../concepts/certificaterequest.md#approval). +[`Approved`](../usage/certificaterequest.md#approval). If the `CertificateRequest` is not `Approved`, the issuer **must** not process it. Issuers are not responsible for approving `CertificateRequests` and should refuse to proceed if they find a certificate that is not approved. -If a `CertificateRequest` created for an issuance associated with a `Certificate` gets [`Denied`](../concepts/certificaterequest.md#approval), the issuance will be failed by cert-manager's issuing controller. +If a `CertificateRequest` created for an issuance associated with a `Certificate` gets [`Denied`](../usage/certificaterequest.md#approval), the issuance will be failed by cert-manager's issuing controller. ## Conditions @@ -65,7 +65,7 @@ status of that resource to a ready state, as this is what is used to signal to h controllers - such as the `Certificate` controller - that the resource is ready to be consumed. Conversely, if the `CertificateRequest` fails, it is as important to mark the resource as such, as this will -also be used as a signal to higher order controllers. Valid condition states are listed under [concepts](../concepts/certificaterequest.md#conditions). +also be used as a signal to higher order controllers. Valid condition states are listed under [concepts](../usage/certificaterequest.md#conditions). ## Implementation diff --git a/content/docs/policy/approval/README.md b/content/docs/policy/approval/README.md index 05f1068f3b..9374fbb374 100644 --- a/content/docs/policy/approval/README.md +++ b/content/docs/policy/approval/README.md @@ -13,10 +13,10 @@ that rejects the request. ## Rejecting requests before sending the X.509 Certificate Signing Request (CSR) to the issuer -cert-manager requires that a [CertificateRequest](../../concepts/certificaterequest.md) +cert-manager requires that a [CertificateRequest](../../usage/certificaterequest.md) is approved before it is sent to the issuer. Also, CertificateSigningRequests must be approved before they are sent to the issuer. This approval is done by adding an -[approval condition](../../concepts/certificaterequest.md#approval) to the resource. +[approval condition](../../usage/certificaterequest.md#approval) to the resource. In a default installation, cert-manager automatically approves all CertificateRequests and CertificateSigningRequests that use any of its built-in issuers. This is done to diff --git a/content/docs/policy/approval/approver-policy/README.md b/content/docs/policy/approval/approver-policy/README.md index aa6084f2ab..76131015f7 100644 --- a/content/docs/policy/approval/approver-policy/README.md +++ b/content/docs/policy/approval/approver-policy/README.md @@ -4,14 +4,14 @@ description: 'Policy plugin for cert-manager' --- approver-policy is a cert-manager -[approver](../../../concepts/certificaterequest.md#approval) +[approver](../../../usage/certificaterequest.md#approval) that will approve or deny CertificateRequests based on policies defined in the `CertificateRequestPolicy` custom resource. ## Prerequisites [cert-manager must be installed](../../../installation/README.md), and -the [the default approver in cert-manager must be disabled](../../../concepts/certificaterequest.md#approver-controller). +the [the default approver in cert-manager must be disabled](../../../usage/certificaterequest.md#approver-controller). > ⚠️ If the default approver is not disabled in cert-manager, approver-policy will > race with cert-manager and policy will be ineffective. @@ -69,7 +69,7 @@ If you are using approver-policy with [external issuers](../../../configuration/external.md), you _must_ include their signer names so that approver-policy has permissions to approve and deny CertificateRequests that -[reference them](../../../concepts/certificaterequest.md#rbac-syntax). +[reference them](../../../usage/certificaterequest.md#rbac-syntax). For example, if using approver-policy for the internal issuer types, along with [google-cas-issuer](https://github.com/jetstack/google-cas-issuer), and [aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer), diff --git a/content/docs/reference/cmctl.md b/content/docs/reference/cmctl.md index 3b79561427..d180dec3e3 100644 --- a/content/docs/reference/cmctl.md +++ b/content/docs/reference/cmctl.md @@ -74,7 +74,7 @@ Use "cmctl [command] --help" for more information about a command. ### Approve and Deny CertificateRequests CertificateRequests can be -[approved or denied](../concepts/certificaterequest.md#approval) using their +[approved or denied](../usage/certificaterequest.md#approval) using their respective cmctl commands: > **Note**: The internal cert-manager approver may automatically approve all diff --git a/content/docs/usage/certificaterequest.md b/content/docs/usage/certificaterequest.md index 6f1d092948..8d39d91499 100644 --- a/content/docs/usage/certificaterequest.md +++ b/content/docs/usage/certificaterequest.md @@ -4,7 +4,7 @@ description: 'cert-manager core concepts: CertificateRequests' --- The `CertificateRequest` is a namespaced resource in cert-manager that is used -to request X.509 certificates from an [`Issuer`](./issuer.md). The resource +to request X.509 certificates from an [`Issuer`](../concepts/issuer.md). The resource contains a base64 encoded string of a PEM encoded certificate request which is sent to the referenced issuer. A successful issuance will return a signed certificate, based on the certificate signing request. `CertificateRequests` are diff --git a/content/docs/usage/csi-driver-spiffe.md b/content/docs/usage/csi-driver-spiffe.md index 771ce6a1ae..ca52c22694 100644 --- a/content/docs/usage/csi-driver-spiffe.md +++ b/content/docs/usage/csi-driver-spiffe.md @@ -46,7 +46,7 @@ which is used to create and mount Pod volumes from. When a Pod is created with the CSI volume configured, the driver will locally generate a private key, and create a cert-manager -[CertificateRequest](../concepts/certificaterequest.md) +[CertificateRequest](../usage/certificaterequest.md) in the same Namespace as the Pod. The driver uses [CSI Token Request](https://kubernetes-csi.github.io/docs/token-requests.html) to both @@ -61,7 +61,7 @@ expiry of the signed certificate. #### Approver -A distinct [cert-manager approver](../concepts/certificaterequest.md#approval) +A distinct [cert-manager approver](../usage/certificaterequest.md#approval) Deployment is responsible for managing the approval and denial condition of created CertificateRequests that target the configured SPIFFE Trust Domain signer. @@ -78,7 +78,7 @@ The approver ensures that requests have: If any of these checks do not pass, the CertificateRequest will be marked as Denied, else it will be marked as Approved. The approver will only manage -CertificateRequests who request from the same [IssuerRef](../concepts/certificaterequest.md) +CertificateRequests who request from the same [IssuerRef](../usage/certificaterequest.md) that has been configured. ## Installation @@ -98,7 +98,7 @@ cert-manager `v1.3` or higher is also required. csi-driver-spiffe requires cert-manager to be [installed](../installation/README.md) but a default installation of cert-manager **will not work**. -> ⚠️ It is **vital** that the [default approver is disabled in cert-manager](../concepts/certificaterequest.md#approver-controller) ⚠️ +> ⚠️ It is **vital** that the [default approver is disabled in cert-manager](../usage/certificaterequest.md#approver-controller) ⚠️ If the default approver is not disabled, the csi-driver-spiffe approver will race with cert-manager and policy enforcement will become useless. @@ -149,7 +149,7 @@ cmctl approve -n cert-manager \ Install csi-driver-spiffe into the cluster using the issuer we configured. We must also configure the issuer resource type and name of the issuer we -configured so that the approver has [permissions to approve referencing CertificateRequests](../concepts/certificaterequest.md#rbac-syntax). +configured so that the approver has [permissions to approve referencing CertificateRequests](../usage/certificaterequest.md#rbac-syntax). Note that the `issuer.name`, `issuer.kind` and `issuer.group` will need to be changed to match the issuer you're actually using! diff --git a/content/docs/usage/csi-driver.md b/content/docs/usage/csi-driver.md index f241e7bb9b..12428534ce 100644 --- a/content/docs/usage/csi-driver.md +++ b/content/docs/usage/csi-driver.md @@ -182,7 +182,7 @@ volumeAttributes: ## Requesting Certificates using the mounting Pod's ServiceAccount If the flag `--use-token-request` is enabled on the csi-driver DaemonSet, the -[CertificateRequest](../concepts/certificaterequest.md) resource will be created +[CertificateRequest](../usage/certificaterequest.md) resource will be created by the mounting Pod's ServiceAccount. This can be paired with [approver-policy](../policy/approval/approver-policy/README.md) to enable advanced policy control on a per-ServiceAccount basis. diff --git a/content/docs/usage/kube-csr.md b/content/docs/usage/kube-csr.md index a4ff6a1a53..0f7c41a3df 100644 --- a/content/docs/usage/kube-csr.md +++ b/content/docs/usage/kube-csr.md @@ -6,7 +6,7 @@ description: 'cert-manager usage: Kubernetes CertificateSigningRequest resources Kubernetes has an in-built [CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) resource. This resource is similar to the cert-manager -[CertificateRequest](../concepts/certificaterequest.md) in that it is used to +[CertificateRequest](../usage/certificaterequest.md) in that it is used to request an X.509 signed certificate from a referenced Certificate Authority (CA). From ebbb7646287314a34af4687e0520089b99b64d9f Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:57:18 +0200 Subject: [PATCH 192/264] improve titles Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 4 ++++ content/docs/usage/certificate.md | 5 ++++- content/docs/usage/certificaterequest.md | 5 ++++- content/docs/usage/gateway.md | 5 ++++- content/docs/usage/ingress.md | 5 ++++- content/docs/usage/kube-csr.md | 5 ++++- 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.spelling b/.spelling index dfe9873015..036797e453 100644 --- a/.spelling +++ b/.spelling @@ -656,6 +656,10 @@ arukiidou Richardds kahirokunn selfsigned-issuer +apiVersion +gateway.networking.k8s.io +networking.k8s.io +certificates.k8s.io # TEMPORARY # these are temporarily ignored because the spellchecker diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index ca29de1e47..56087a2444 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -1,8 +1,11 @@ --- -title: cert-manager.io Certificate +title: Certificate resource description: 'cert-manager usage: Certificates' --- +> **apiVersion:** cert-manager.io/v1 +> **kind:** Certificate + In cert-manager, the [`Certificate`](../concepts/certificate.md) resource represents a human readable definition of a certificate request that is to be honored by an issuer which is to be kept up-to-date. This is the usual way that diff --git a/content/docs/usage/certificaterequest.md b/content/docs/usage/certificaterequest.md index 8d39d91499..62531a6dda 100644 --- a/content/docs/usage/certificaterequest.md +++ b/content/docs/usage/certificaterequest.md @@ -1,8 +1,11 @@ --- -title: cert-manager.io CertificateRequest +title: CertificateRequest resource description: 'cert-manager core concepts: CertificateRequests' --- +> **apiVersion:** cert-manager.io/v1 +> **kind:** CertificateRequest + The `CertificateRequest` is a namespaced resource in cert-manager that is used to request X.509 certificates from an [`Issuer`](../concepts/issuer.md). The resource contains a base64 encoded string of a PEM encoded certificate request which is diff --git a/content/docs/usage/gateway.md b/content/docs/usage/gateway.md index ae852ef6b7..d331fbed9d 100644 --- a/content/docs/usage/gateway.md +++ b/content/docs/usage/gateway.md @@ -1,8 +1,11 @@ --- -title: Annotated gateway.networking.k8s.io Gateway +title: Annotated Gateway resource description: 'cert-manager usage: Kubernetes Gateways' --- +> **apiVersion:** gateway.networking.k8s.io/v1alpha2 +> **kind:** Gateway + **FEATURE STATE**: cert-manager 1.5 [alpha]
              diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index af82abbd0a..f12d921e76 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -1,8 +1,11 @@ --- -title: Annotated networking.k8s.io Ingress +title: Annotated Ingress resource description: 'cert-manager usage: Kubernetes Ingress' --- +> **apiVersion:** networking.k8s.io/v1 +> **kind:** Ingress + A common use-case for cert-manager is requesting TLS signed certificates to secure your ingress resources. This can be done by simply adding annotations to your `Ingress` resources and cert-manager will facilitate creating the diff --git a/content/docs/usage/kube-csr.md b/content/docs/usage/kube-csr.md index 0f7c41a3df..d3b39f6212 100644 --- a/content/docs/usage/kube-csr.md +++ b/content/docs/usage/kube-csr.md @@ -1,8 +1,11 @@ --- -title: certificates.k8s.io CertificateSigningRequest +title: CertificateSigningRequest resource description: 'cert-manager usage: Kubernetes CertificateSigningRequest resources' --- +> **apiVersion:** certificates.k8s.io/v1 +> **kind:** CertificateSigningRequest + Kubernetes has an in-built [CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) resource. This resource is similar to the cert-manager From ec94f3d00965cd571f6d2fc2e6e6ec428cbcc4ab Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 12 Oct 2023 09:36:28 +0200 Subject: [PATCH 193/264] remove menu icons Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/manifest.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 005a1d6d20..8d3ec903f1 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -411,23 +411,23 @@ "path": "/docs/usage/README.md" }, { - "title": "⚓ Certificate", + "title": "Certificate", "path": "/docs/usage/certificate.md" }, { - "title": "⚓ CertificateRequest", + "title": "CertificateRequest", "path": "/docs/usage/certificaterequest.md" }, { - "title": "☸️ Ingress", + "title": "Ingress", "path": "/docs/usage/ingress.md" }, { - "title": "☸️ Gateway", + "title": "Gateway", "path": "/docs/usage/gateway.md" }, { - "title": "☸️ CertificateSigningRequests", + "title": "CertificateSigningRequests", "path": "/docs/usage/kube-csr.md" }, { From 9a04bd4ea01de6ec0a20b97c1460ff8f00759b90 Mon Sep 17 00:00:00 2001 From: Pieter van der Giessen Date: Thu, 12 Oct 2023 15:50:32 +0200 Subject: [PATCH 194/264] Update Google DNS doc in case custom role is used Signed-off-by: Pieter van der Giessen --- content/docs/configuration/acme/dns01/google.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/docs/configuration/acme/dns01/google.md b/content/docs/configuration/acme/dns01/google.md index 460239de80..b9b6db839a 100644 --- a/content/docs/configuration/acme/dns01/google.md +++ b/content/docs/configuration/acme/dns01/google.md @@ -45,7 +45,8 @@ gcloud projects add-iam-policy-binding $PROJECT_ID \ > * `dns.resourceRecordSets.*` > * `dns.changes.*` > * `dns.managedZones.list` - +> +> In case you do not use the `dns.admin` role, you will also need to make sure that the Service Account used by your GKE cluster (e.g. the Compute Engine default service account) has the `https://www.googleapis.com/auth/cloud-platform` access scope assigned to it. See [Access scopes in GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/access-scopes). ## Use Static Credentials Follow the instructions in the following sections to deploy cert-manager using From 24fdec14aee9aff0e7da07e1591dd2d697922220 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 17 Oct 2023 10:57:15 +0200 Subject: [PATCH 195/264] add diagrams to the requesting certificates pages based on Mael's e2e diagram Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/usage/certificate.md | 6 + content/docs/usage/certificaterequest.md | 6 +- content/docs/usage/gateway.md | 6 + content/docs/usage/ingress.md | 6 + content/docs/usage/kube-csr.md | 4 + .../certificate-flow.drawio | 285 ++++++++++++++++++ .../certificate-flow.svg | 3 + .../certificate-request-flow.drawio | 68 +++++ .../certificate-request-flow.svg | 3 + .../certificate-signing-request-flow.drawio | 72 +++++ .../certificate-signing-request-flow.svg | 3 + .../csi-driver-flow.drawio | 103 +++++++ .../csi-driver-spiffe-flow.drawio | 104 +++++++ .../gateway-shim-flow.drawio | 107 +++++++ .../gateway-shim-flow.svg | 3 + .../ingress-shim-flow.drawio | 104 +++++++ .../ingress-shim-flow.svg | 3 + 17 files changed, 885 insertions(+), 1 deletion(-) create mode 100644 public/images/request-certificate-debug/certificate-flow.drawio create mode 100644 public/images/request-certificate-debug/certificate-flow.svg create mode 100644 public/images/request-certificate-debug/certificate-request-flow.drawio create mode 100644 public/images/request-certificate-debug/certificate-request-flow.svg create mode 100644 public/images/request-certificate-debug/certificate-signing-request-flow.drawio create mode 100644 public/images/request-certificate-debug/certificate-signing-request-flow.svg create mode 100644 public/images/request-certificate-debug/csi-driver-flow.drawio create mode 100644 public/images/request-certificate-debug/csi-driver-spiffe-flow.drawio create mode 100644 public/images/request-certificate-debug/gateway-shim-flow.drawio create mode 100644 public/images/request-certificate-debug/gateway-shim-flow.svg create mode 100644 public/images/request-certificate-debug/ingress-shim-flow.drawio create mode 100644 public/images/request-certificate-debug/ingress-shim-flow.svg diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index 56087a2444..af6b9311d5 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -373,3 +373,9 @@ data: key.der: ... ``` + +## Inner workings diagram + + + +[1] https://cert-manager.io/docs/usage/certificaterequest diff --git a/content/docs/usage/certificaterequest.md b/content/docs/usage/certificaterequest.md index 62531a6dda..1a00b112f2 100644 --- a/content/docs/usage/certificaterequest.md +++ b/content/docs/usage/certificaterequest.md @@ -261,4 +261,8 @@ and `bar`: ```yaml resourceNames: ["myissuers.my-example.io/foo.myapp", "myissuers.my-example.io/bar.myapp"] -``` \ No newline at end of file +``` + +## Inner workings diagram + + diff --git a/content/docs/usage/gateway.md b/content/docs/usage/gateway.md index d331fbed9d..cc6a093b92 100644 --- a/content/docs/usage/gateway.md +++ b/content/docs/usage/gateway.md @@ -439,3 +439,9 @@ Certificate resources: - `cert-manager.io/private-key-rotation-policy`: (optional) this annotation allows you to configure `spec.privateKey.rotationPolicy` field to set the rotation policy of the private key for a Certificate. Valid values are `Never` and `Always`. If unset a rotation policy `Never` will be used. + +## Inner workings diagram + + + +[1] https://cert-manager.io/docs/usage/certificate diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index f12d921e76..7bcd98632c 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -216,3 +216,9 @@ guide](../installation/README.md). ## Troubleshooting If you do not see a `Certificate` resource being created after applying the ingress-shim annotations check that at least `cert-manager.io/issuer` or `cert-manager.io/cluster-issuer` is set. If you want to use `kubernetes.io/tls-acme: "true"` make sure to have checked all steps above and you might want to look for errors in the cert-manager pod logs if not resolved. + +## Inner workings diagram + + + +[1] https://cert-manager.io/docs/usage/certificate diff --git a/content/docs/usage/kube-csr.md b/content/docs/usage/kube-csr.md index d3b39f6212..2cee76f9cb 100644 --- a/content/docs/usage/kube-csr.md +++ b/content/docs/usage/kube-csr.md @@ -167,3 +167,7 @@ are not approved by default, so you will likely need to approve it manually: ```bash $ kubectl certificate approve ``` + +## Inner workings diagram + + diff --git a/public/images/request-certificate-debug/certificate-flow.drawio b/public/images/request-certificate-debug/certificate-flow.drawio new file mode 100644 index 0000000000..aae0008481 --- /dev/null +++ b/public/images/request-certificate-debug/certificate-flow.drawio @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/request-certificate-debug/certificate-flow.svg b/public/images/request-certificate-debug/certificate-flow.svg new file mode 100644 index 0000000000..11bc5717ec --- /dev/null +++ b/public/images/request-certificate-debug/certificate-flow.svg @@ -0,0 +1,3 @@ + + +
              kind: CertificateRequest
              metadata:
                name: cert-1-ab0123
                annotations:
                  cert-manager.io/certificate-revision: "1"
              spec:
                issuerRef: issuer-1
                request: |
                  -----BEGIN CERTIFICATE REQUEST-----
                  ...
                  -----END CERTIFICATE REQUEST-----

              kind: CertificateRequest...
              kind: Issuer
              metadata:
                name: issuer-1
              spec: ...
              kind: Issuer...
              user creates the Certificate "cert-1"
              user creates the Certificate "cert-1"
              triggercontroller marks for reissuance
              triggercontroller marks f...
              kind: Certificate
              metadata:
                name: cert-1
              spec:
                dnsNames:
                  - example.com
                  - foo.example.com
                issuerRef: issuer-1
                secretName: sec-1
              kind: Certificate...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                conditions:
                - type: Issuing
                  status: "True"
                  reason: Pending
              kind: Certificate...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                nextPrivateKeySecret: sec-1-01ab4f
                conditions:
                - type: Issuing
                  status: "True"
                  reason: Pending
              kind: Certificate...
              keymanager creates a temporary private key
              keymanager creates a tempora...
              kind: Secret
              metadata:
                name: sec-1-01ab4f
              stringData:
                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----

              kind: Secret...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                nextPrivateKeySecret: sec-1-01ab4f
                revision: nil
                conditions:
                - type: Issuing
                  status: "True"
                  reason: Pending
              kind: Certificate...
              (a) requestmanager creates CertificateRequest
                  with revision = "1" (the next revision)
              (a) requestmanager creates CertificateRequest...
              (b) requestmanager signs the CSR      using the private key
              (b) requestmanager signs the CSR      usi...
              (a)
              (a)
              (b)
              (b)
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                nextPrivateKeySecret: sec-1-01ab4f
                revision: 1
                conditions:
                  - type: Issuing
                    status: "True"
                    reason: Pending
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

              kind: Certificate...
              (b) issuing
                  controller              bubbles up the          certificate
              (b) issuing...
              (a) issuing
                  controller
                  sets
                  revision = 1
              (a) issuing...
              TEMPORARY SECRET
              TEMPORARY SECRET
              kind: Secret
              metadata:
                name: sec-1-01ab4f
              stringData:
                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----

              kind: Secret...
              kind: Secret
              metadata:
                name: sec-1
              stringData:
                tls.crt: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

                ca.crt: ""
                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----
              kind: Secret...
              TEMPORARY SECRET
              TEMPORARY SECRET
              kind: CertificateRequest
              metadata:
                name: cert-1-ab0123
                annotations:
                  cert-manager.io/certificate-revision: "1"
              status:
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----


              kind: CertificateRequest...
              STAYS FOREVER
              (by default)
              STAYS FOREVER...
              (d) issuing controller
                  sets tls.crt
                  and ca.crt(if returned by CA)
              (d) issuing controller...
              REMOVED AFTER USE
              REMOVED AFTER U...
              (c) issuing
                  controller
                  creates sec-1
              (c) issuing...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                revision: 1
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                  - type: Issuing
                    status: "True"
                    reason: Pending
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----
              kind: Certificate...
              (e) issuing controller
                  copies tls.key
                  from temporary
                  private key
              (e) issuing controller...
              ready controller
              sets readiness
              ready controller...
              DOES NOT EXIST YET
              DOES NOT EXI...
              ISSUER
              ISSUER
              CertificateRequest
              Flow [1]
              CertificateRequest...
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-debug/certificate-request-flow.drawio b/public/images/request-certificate-debug/certificate-request-flow.drawio new file mode 100644 index 0000000000..06e17205a1 --- /dev/null +++ b/public/images/request-certificate-debug/certificate-request-flow.drawio @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/request-certificate-debug/certificate-request-flow.svg b/public/images/request-certificate-debug/certificate-request-flow.svg new file mode 100644 index 0000000000..912ce1cc3f --- /dev/null +++ b/public/images/request-certificate-debug/certificate-request-flow.svg @@ -0,0 +1,3 @@ + + +
              kind: CertificateRequest
              metadata:
                name: cert-1-ab0123
              spec:
                issuerRef: issuer-1
                request: |
                  -----BEGIN CERTIFICATE REQUEST-----
                  ...
                  -----END CERTIFICATE REQUEST-----

              kind: CertificateRequest...
              a controller generates a CertificateRequest
              a controller generates a Certi...
              ISSUER IMPLEMENTATION
              ISSUER IMPLEMENTATION
              kind: CertificateRequest
              metadata:
                name: cert-1-ab0123
              status:
                conditions:
                  - type: Approved
                    status: "True"
                    reason: policy.cert-manager.io
                  - type: Ready
                    status: "True"
                    reason: Issued
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----


              kind: CertificateRequest...
              Validate CertificateRequest
              Validate CertificateRequest
              kind: CertificateRequest
              metadata:
                name: cert-1-ab0123
              status:
                conditions:
                  - type: Approved
                    status: "True"
                    reason: policy.cert-manager.io
              kind: CertificateRequest...
              a cert-manager approver approves the CertificateRequest
              a cert-manager approver approves the C...
              Generate a Certificate using the CertificateRequest as input

              ⚠️the public key is the only certificate attribute that is guaranteed to match the request
              Generate a Certificate using the CertificateRequest as inpu...
              Sign Certificate
              Sign Certificate
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-debug/certificate-signing-request-flow.drawio b/public/images/request-certificate-debug/certificate-signing-request-flow.drawio new file mode 100644 index 0000000000..4740973e2a --- /dev/null +++ b/public/images/request-certificate-debug/certificate-signing-request-flow.drawio @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/request-certificate-debug/certificate-signing-request-flow.svg b/public/images/request-certificate-debug/certificate-signing-request-flow.svg new file mode 100644 index 0000000000..d94ca54293 --- /dev/null +++ b/public/images/request-certificate-debug/certificate-signing-request-flow.svg @@ -0,0 +1,3 @@ + + +
              apiVersion: certificates.k8s.io/v1
              kind: CertificateSigningRequest
              metadata:
                name: cert-req-1
              spec:
                signerName: clusterissuers.cert-manager.io/my-cluster-issuer
                request: |
                  -----BEGIN CERTIFICATE REQUEST-----
                  ...
                  -----END CERTIFICATE REQUEST-----

              apiVersion: certificates.k8s.io/v1...
              a controller generates a CertificateRequest
              a controller generates a Certi...
              ISSUER IMPLEMENTATION
              ISSUER IMPLEMENTATION
              apiVersion: certificates.k8s.io/v1
              kind: CertificateSigningRequest
              metadata:
                name: cert-req-1
              status:
                conditions:
                  - type: Approved
                    status: "True"
                    reason: policy.cert-manager.io
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----
              apiVersion: certificates.k8s.io/v1kind: CertificateSigni...
              Validate CertificateRequest
              Validate CertificateRequest
              apiVersion: certificates.k8s.io/v1
              kind: CertificateSigningRequest
              metadata:
                name: cert-req-1
              status:
                conditions:
                  - type: Approved
                    status: "True"
                    reason: policy.cert-manager.io
              apiVersion: certificates.k8s.io/v1kind: CertificateSign...
              kubectl certificate approve cert-req-1
              kubectl certificate approve cert-req-1
              Generate a Certificate using the CertificateRequest as input

              ⚠️the public key is the only certificate attribute that is guaranteed to match the request
              Generate a Certificate using the CertificateRequest as inpu...
              Sign Certificate
              Sign Certificate
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-debug/csi-driver-flow.drawio b/public/images/request-certificate-debug/csi-driver-flow.drawio new file mode 100644 index 0000000000..7b101ac716 --- /dev/null +++ b/public/images/request-certificate-debug/csi-driver-flow.drawio @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/request-certificate-debug/csi-driver-spiffe-flow.drawio b/public/images/request-certificate-debug/csi-driver-spiffe-flow.drawio new file mode 100644 index 0000000000..06213da87e --- /dev/null +++ b/public/images/request-certificate-debug/csi-driver-spiffe-flow.drawio @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/request-certificate-debug/gateway-shim-flow.drawio b/public/images/request-certificate-debug/gateway-shim-flow.drawio new file mode 100644 index 0000000000..51908f0f2b --- /dev/null +++ b/public/images/request-certificate-debug/gateway-shim-flow.drawio @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/request-certificate-debug/gateway-shim-flow.svg b/public/images/request-certificate-debug/gateway-shim-flow.svg new file mode 100644 index 0000000000..e08d309947 --- /dev/null +++ b/public/images/request-certificate-debug/gateway-shim-flow.svg @@ -0,0 +1,3 @@ + + +
              kind: Issuer
              metadata:
                name: issuer-1
              spec: ...
              kind: Issuer...
              gateway-shim creates the Certificate "cert-1"
              gateway-shim creates the Certificat...
              kind: Certificate
              metadata:
                name: cert-1
              spec:
                dnsNames:
                  - example.com
                issuerRef: issuer-1
                secretName: cert-1
              kind: Certificate...
              DOES NOT EXIST YET
              DOES NOT EXI...
              CERT-MANAGER ISSUER
              CERT-MANAGER ISSUER
              apiVersion: gateway.networking.k8s.io/v1alpha2
              kind: Gateway
              metadata:
                name: gateway-1
                annotations:
                  cert-manager.io/issuer: issuer-1
              spec:
                listeners:
                  - hostname: example.com
                    tls:
                      mode: Terminate
                      certificateRefs:
                        - name: cert-1
              apiVersion: gateway.networking.k8s.io/v1alpha2kind: Gate...
              user creates a Gateway "gateway-1" with cert-manager annotations
              user creates a Gateway "gateway-...
              kind: Secret
              metadata:
                name: cert-1
              stringData:
                tls.crt: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----
              kind: Secret...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: cert-1
              status:
                revision: 1
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                  - type: Issuing
                    status: "True"
                    reason: Pending
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----
              kind: Certificate...
              Certificate Flow [1]
              Certificate Flow [1]
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-debug/ingress-shim-flow.drawio b/public/images/request-certificate-debug/ingress-shim-flow.drawio new file mode 100644 index 0000000000..06213da87e --- /dev/null +++ b/public/images/request-certificate-debug/ingress-shim-flow.drawio @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/request-certificate-debug/ingress-shim-flow.svg b/public/images/request-certificate-debug/ingress-shim-flow.svg new file mode 100644 index 0000000000..3540a4809d --- /dev/null +++ b/public/images/request-certificate-debug/ingress-shim-flow.svg @@ -0,0 +1,3 @@ + + +
              kind: Issuer
              metadata:
                name: issuer-1
              spec: ...
              kind: Issuer...
              ingress-shim creates the Certificate "cert-1"
              ingress-shim creates the Certificat...
              kind: Certificate
              metadata:
                name: cert-1
              spec:
                dnsNames:
                  - example.com
                  - foo.example.com
                issuerRef: issuer-1
                secretName: cert-1
              kind: Certificate...
              DOES NOT EXIST YET
              DOES NOT EXI...
              CERT-MANAGER ISSUER
              CERT-MANAGER ISSUER
              kind: Ingress
              metadata:
                name: ingress-1
                annotations:
                  cert-manager.io/issuer: issuer-1
              tls:
                - hosts:
                  - example.com
                  - foo.example.com
                  secretName: cert-1
              kind: Ingress...
              user creates an Ingress "ingress-1" with cert-manager annotations
              user creates an Ingress "ingress...
              kind: Secret
              metadata:
                name: cert-1
              stringData:
                tls.crt: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----
              kind: Secret...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: cert-1
              status:
                revision: 1
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                  - type: Issuing
                    status: "True"
                    reason: Pending
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----
              kind: Certificate...
              Certificate Flow [1]
              Certificate Flow [1]
              Text is not SVG - cannot display
              \ No newline at end of file From a52cf3f70b949562bf3c17ac3f2a3cb600a3cd2f Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:52:39 +0200 Subject: [PATCH 196/264] fix bugs in diagrams & improve titles Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/usage/certificate.md | 2 +- content/docs/usage/certificaterequest.md | 2 +- content/docs/usage/gateway.md | 2 +- content/docs/usage/ingress.md | 2 +- content/docs/usage/kube-csr.md | 2 +- .../certificate-flow.drawio | 149 +++++++++--------- .../certificate-flow.svg | 2 +- .../gateway-shim-flow.drawio | 34 ++-- .../gateway-shim-flow.svg | 2 +- .../ingress-shim-flow.drawio | 10 +- .../ingress-shim-flow.svg | 2 +- 11 files changed, 105 insertions(+), 104 deletions(-) diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index af6b9311d5..3882fba3c3 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -374,7 +374,7 @@ data: ... ``` -## Inner workings diagram +## Inner workings diagram for developers diff --git a/content/docs/usage/certificaterequest.md b/content/docs/usage/certificaterequest.md index 1a00b112f2..2c43845d1e 100644 --- a/content/docs/usage/certificaterequest.md +++ b/content/docs/usage/certificaterequest.md @@ -263,6 +263,6 @@ and `bar`: resourceNames: ["myissuers.my-example.io/foo.myapp", "myissuers.my-example.io/bar.myapp"] ``` -## Inner workings diagram +## Inner workings diagram for developers diff --git a/content/docs/usage/gateway.md b/content/docs/usage/gateway.md index cc6a093b92..905344a66e 100644 --- a/content/docs/usage/gateway.md +++ b/content/docs/usage/gateway.md @@ -440,7 +440,7 @@ Certificate resources: configure `spec.privateKey.rotationPolicy` field to set the rotation policy of the private key for a Certificate. Valid values are `Never` and `Always`. If unset a rotation policy `Never` will be used. -## Inner workings diagram +## Inner workings diagram for developers diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index 7bcd98632c..b002c62841 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -217,7 +217,7 @@ guide](../installation/README.md). If you do not see a `Certificate` resource being created after applying the ingress-shim annotations check that at least `cert-manager.io/issuer` or `cert-manager.io/cluster-issuer` is set. If you want to use `kubernetes.io/tls-acme: "true"` make sure to have checked all steps above and you might want to look for errors in the cert-manager pod logs if not resolved. -## Inner workings diagram +## Inner workings diagram for developers diff --git a/content/docs/usage/kube-csr.md b/content/docs/usage/kube-csr.md index 2cee76f9cb..ac1646db8b 100644 --- a/content/docs/usage/kube-csr.md +++ b/content/docs/usage/kube-csr.md @@ -168,6 +168,6 @@ are not approved by default, so you will likely need to approve it manually: $ kubectl certificate approve ``` -## Inner workings diagram +## Inner workings diagram for developers diff --git a/public/images/request-certificate-debug/certificate-flow.drawio b/public/images/request-certificate-debug/certificate-flow.drawio index aae0008481..3f77bca585 100644 --- a/public/images/request-certificate-debug/certificate-flow.drawio +++ b/public/images/request-certificate-debug/certificate-flow.drawio @@ -1,9 +1,24 @@ - + - + + + + + + + + + + + + + + + + @@ -96,7 +111,7 @@ - + @@ -109,13 +124,13 @@ - - + + - - + + - + @@ -129,8 +144,8 @@ - - + + @@ -138,102 +153,79 @@ - - - - - + + - - + + - + - + - + - + - - - + + + - - + + - - - - - - - - - - + - - - - - - - - + + - + - - - - + - - + + - - - - + - - + + - - - + + + + - - + + - - + + @@ -241,17 +233,11 @@ - - - - - - - - + + - + @@ -275,10 +261,25 @@ - + + + + + + + + + + + + + + + + diff --git a/public/images/request-certificate-debug/certificate-flow.svg b/public/images/request-certificate-debug/certificate-flow.svg index 11bc5717ec..0c675475a2 100644 --- a/public/images/request-certificate-debug/certificate-flow.svg +++ b/public/images/request-certificate-debug/certificate-flow.svg @@ -1,3 +1,3 @@ -
              kind: CertificateRequest
              metadata:
                name: cert-1-ab0123
                annotations:
                  cert-manager.io/certificate-revision: "1"
              spec:
                issuerRef: issuer-1
                request: |
                  -----BEGIN CERTIFICATE REQUEST-----
                  ...
                  -----END CERTIFICATE REQUEST-----

              kind: CertificateRequest...
              kind: Issuer
              metadata:
                name: issuer-1
              spec: ...
              kind: Issuer...
              user creates the Certificate "cert-1"
              user creates the Certificate "cert-1"
              triggercontroller marks for reissuance
              triggercontroller marks f...
              kind: Certificate
              metadata:
                name: cert-1
              spec:
                dnsNames:
                  - example.com
                  - foo.example.com
                issuerRef: issuer-1
                secretName: sec-1
              kind: Certificate...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                conditions:
                - type: Issuing
                  status: "True"
                  reason: Pending
              kind: Certificate...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                nextPrivateKeySecret: sec-1-01ab4f
                conditions:
                - type: Issuing
                  status: "True"
                  reason: Pending
              kind: Certificate...
              keymanager creates a temporary private key
              keymanager creates a tempora...
              kind: Secret
              metadata:
                name: sec-1-01ab4f
              stringData:
                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----

              kind: Secret...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                nextPrivateKeySecret: sec-1-01ab4f
                revision: nil
                conditions:
                - type: Issuing
                  status: "True"
                  reason: Pending
              kind: Certificate...
              (a) requestmanager creates CertificateRequest
                  with revision = "1" (the next revision)
              (a) requestmanager creates CertificateRequest...
              (b) requestmanager signs the CSR      using the private key
              (b) requestmanager signs the CSR      usi...
              (a)
              (a)
              (b)
              (b)
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                nextPrivateKeySecret: sec-1-01ab4f
                revision: 1
                conditions:
                  - type: Issuing
                    status: "True"
                    reason: Pending
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

              kind: Certificate...
              (b) issuing
                  controller              bubbles up the          certificate
              (b) issuing...
              (a) issuing
                  controller
                  sets
                  revision = 1
              (a) issuing...
              TEMPORARY SECRET
              TEMPORARY SECRET
              kind: Secret
              metadata:
                name: sec-1-01ab4f
              stringData:
                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----

              kind: Secret...
              kind: Secret
              metadata:
                name: sec-1
              stringData:
                tls.crt: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

                ca.crt: ""
                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----
              kind: Secret...
              TEMPORARY SECRET
              TEMPORARY SECRET
              kind: CertificateRequest
              metadata:
                name: cert-1-ab0123
                annotations:
                  cert-manager.io/certificate-revision: "1"
              status:
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----


              kind: CertificateRequest...
              STAYS FOREVER
              (by default)
              STAYS FOREVER...
              (d) issuing controller
                  sets tls.crt
                  and ca.crt(if returned by CA)
              (d) issuing controller...
              REMOVED AFTER USE
              REMOVED AFTER U...
              (c) issuing
                  controller
                  creates sec-1
              (c) issuing...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                revision: 1
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                  - type: Issuing
                    status: "True"
                    reason: Pending
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----
              kind: Certificate...
              (e) issuing controller
                  copies tls.key
                  from temporary
                  private key
              (e) issuing controller...
              ready controller
              sets readiness
              ready controller...
              DOES NOT EXIST YET
              DOES NOT EXI...
              ISSUER
              ISSUER
              CertificateRequest
              Flow [1]
              CertificateRequest...
              Text is not SVG - cannot display
              \ No newline at end of file +
              kind: CertificateRequest
              metadata:
                name: cert-1-ab0123
                annotations:
                  cert-manager.io/certificate-revision: "1"
              status:
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----


              kind: CertificateRequest...
              STAYS FOREVER
              (by default)
              STAYS FOREVER...
              kind: CertificateRequest
              metadata:
                name: cert-1-ab0123
                annotations:
                  cert-manager.io/certificate-revision: "1"
              spec:
                issuerRef: issuer-1
                request: |
                  -----BEGIN CERTIFICATE REQUEST-----
                  ...
                  -----END CERTIFICATE REQUEST-----

              kind: CertificateRequest...
              kind: Issuer
              metadata:
                name: issuer-1
              spec: ...
              kind: Issuer...
              user creates the Certificate "cert-1"
              user creates the Certificate "cert-1"
              triggercontroller marks for reissuance
              triggercontroller marks f...
              kind: Certificate
              metadata:
                name: cert-1
              spec:
                dnsNames:
                  - example.com
                  - foo.example.com
                issuerRef: issuer-1
                secretName: sec-1
              kind: Certificate...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                conditions:
                - type: Issuing
                  status: "True"
                  reason: Pending
              kind: Certificate...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                nextPrivateKeySecret: sec-1-01ab4f
                conditions:
                - type: Issuing
                  status: "True"
                  reason: Pending
              kind: Certificate...
              keymanager creates a temporary private key
              keymanager creates a tempora...
              kind: Secret
              metadata:
                name: sec-1-01ab4f
              stringData:
                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----

              kind: Secret...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                nextPrivateKeySecret: sec-1-01ab4f
                revision: nil
                conditions:
                - type: Issuing
                  status: "True"
                  reason: Pending
              kind: Certificate...
              (a.1) requestmanager creates CertificateRequest
                    with revision = "1" (the next revision)
              (a.1) requestmanager creates CertificateRequest...
              (a.2) requestmanager signs the CSR
                    using the private key
              (a.2) requestmanager signs the CSR...
              (a.1)
              (a.1)
              (a.2)
              (a.2)
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                nextPrivateKeySecret: sec-1-01ab4f
                revision: 1
                conditions:
                  - type: Issuing
                    status: "False"
                    reason: Issued
              kind: Certificate...
              (c) issuing
                  controller
                  sets
                 
              revision = 1
                  and
                  Issuing = False
              (c) issuing...
              TEMPORARY SECRET
              TEMPORARY SECRET
              kind: Secret
              metadata:
                name: sec-1-01ab4f
              stringData:
                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----

              kind: Secret...
              kind: Secret
              metadata:
                name: sec-1
              stringData:
                tls.crt: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

                ca.crt: ""
                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----
              kind: Secret...
              TEMPORARY SECRET
              TEMPORARY SECRET
              REMOVED AFTER USE
              REMOVED AFTER U...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: sec-1
              status:
                revision: 1
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                  - type: Issuing
                    status: "False"
                    reason: Issued
              kind: Certificate...
              (b.3) issuing controller
                    copies tls.key
                    from temporary
                    private key
              (b.3) issuing controller...
              ready controller
              sets readiness
              ready controller...
              DOES NOT EXIST YET
              DOES NOT EXI...
              ISSUER
              ISSUER
              CertificateRequest
              Flow [1]
              CertificateRequest...
              (b.1) issuing controller
                    creates or updates sec-1
              (b.1) issuing controller...
              (b.2) issuing controller
                    sets tls.crt
                    and ca.crt(if returned by CA)
              (b.2) issuing controller...
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-debug/gateway-shim-flow.drawio b/public/images/request-certificate-debug/gateway-shim-flow.drawio index 51908f0f2b..7afa227611 100644 --- a/public/images/request-certificate-debug/gateway-shim-flow.drawio +++ b/public/images/request-certificate-debug/gateway-shim-flow.drawio @@ -1,6 +1,6 @@ - + - + @@ -8,7 +8,7 @@ - + @@ -20,26 +20,26 @@ - + - + - + - - + + - - + + - + @@ -51,10 +51,10 @@ - + - - + + @@ -66,8 +66,8 @@ - - + + @@ -97,7 +97,7 @@ - + diff --git a/public/images/request-certificate-debug/gateway-shim-flow.svg b/public/images/request-certificate-debug/gateway-shim-flow.svg index e08d309947..cb9aba8d42 100644 --- a/public/images/request-certificate-debug/gateway-shim-flow.svg +++ b/public/images/request-certificate-debug/gateway-shim-flow.svg @@ -1,3 +1,3 @@ -
              kind: Issuer
              metadata:
                name: issuer-1
              spec: ...
              kind: Issuer...
              gateway-shim creates the Certificate "cert-1"
              gateway-shim creates the Certificat...
              kind: Certificate
              metadata:
                name: cert-1
              spec:
                dnsNames:
                  - example.com
                issuerRef: issuer-1
                secretName: cert-1
              kind: Certificate...
              DOES NOT EXIST YET
              DOES NOT EXI...
              CERT-MANAGER ISSUER
              CERT-MANAGER ISSUER
              apiVersion: gateway.networking.k8s.io/v1alpha2
              kind: Gateway
              metadata:
                name: gateway-1
                annotations:
                  cert-manager.io/issuer: issuer-1
              spec:
                listeners:
                  - hostname: example.com
                    tls:
                      mode: Terminate
                      certificateRefs:
                        - name: cert-1
              apiVersion: gateway.networking.k8s.io/v1alpha2kind: Gate...
              user creates a Gateway "gateway-1" with cert-manager annotations
              user creates a Gateway "gateway-...
              kind: Secret
              metadata:
                name: cert-1
              stringData:
                tls.crt: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----
              kind: Secret...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: cert-1
              status:
                revision: 1
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                  - type: Issuing
                    status: "True"
                    reason: Pending
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----
              kind: Certificate...
              Certificate Flow [1]
              Certificate Flow [1]
              Text is not SVG - cannot display
              \ No newline at end of file +
              kind: Issuer
              metadata:
                name: issuer-1
              spec: ...
              kind: Issuer...
              gateway-shim creates the Certificate "cert-1"
              gateway-shim creates the Certificat...
              kind: Certificate
              metadata:
                name: cert-1
              spec:
                dnsNames:
                  - example.com
                issuerRef: issuer-1
                secretName: cert-1
              kind: Certificate...
              DOES NOT EXIST YET
              DOES NOT EXI...
              CERT-MANAGER ISSUER
              CERT-MANAGER ISSUER
              apiVersion: gateway.networking.k8s.io/v1alpha2
              kind: Gateway
              metadata:
                name: gateway-1
                annotations:
                  cert-manager.io/issuer: issuer-1
              spec:
                listeners:
                  - hostname: example.com
                    tls:
                      mode: Terminate
                      certificateRefs:
                        - kind: Secret
                          name: cert-1
              apiVersion: gateway.networking.k8s.io/v1alpha2kind: Gate...
              user creates a Gateway "gateway-1" with cert-manager annotations
              user creates a Gateway "gateway-...
              kind: Secret
              metadata:
                name: cert-1
              stringData:
                tls.crt: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----
              kind: Secret...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: cert-1
              status:
                revision: 1
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                  - type: Issuing
                    status: "False"
                    reason: Issued
              kind: Certificate...
              Certificate Flow [1]
              Certificate Flow [1]
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-debug/ingress-shim-flow.drawio b/public/images/request-certificate-debug/ingress-shim-flow.drawio index 06213da87e..dc9bbb3bf3 100644 --- a/public/images/request-certificate-debug/ingress-shim-flow.drawio +++ b/public/images/request-certificate-debug/ingress-shim-flow.drawio @@ -1,6 +1,6 @@ - + - + @@ -73,8 +73,8 @@
              - - + + @@ -94,7 +94,7 @@ - + diff --git a/public/images/request-certificate-debug/ingress-shim-flow.svg b/public/images/request-certificate-debug/ingress-shim-flow.svg index 3540a4809d..a32812ad01 100644 --- a/public/images/request-certificate-debug/ingress-shim-flow.svg +++ b/public/images/request-certificate-debug/ingress-shim-flow.svg @@ -1,3 +1,3 @@ -
              kind: Issuer
              metadata:
                name: issuer-1
              spec: ...
              kind: Issuer...
              ingress-shim creates the Certificate "cert-1"
              ingress-shim creates the Certificat...
              kind: Certificate
              metadata:
                name: cert-1
              spec:
                dnsNames:
                  - example.com
                  - foo.example.com
                issuerRef: issuer-1
                secretName: cert-1
              kind: Certificate...
              DOES NOT EXIST YET
              DOES NOT EXI...
              CERT-MANAGER ISSUER
              CERT-MANAGER ISSUER
              kind: Ingress
              metadata:
                name: ingress-1
                annotations:
                  cert-manager.io/issuer: issuer-1
              tls:
                - hosts:
                  - example.com
                  - foo.example.com
                  secretName: cert-1
              kind: Ingress...
              user creates an Ingress "ingress-1" with cert-manager annotations
              user creates an Ingress "ingress...
              kind: Secret
              metadata:
                name: cert-1
              stringData:
                tls.crt: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----
              kind: Secret...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: cert-1
              status:
                revision: 1
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                  - type: Issuing
                    status: "True"
                    reason: Pending
                certificate: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----
              kind: Certificate...
              Certificate Flow [1]
              Certificate Flow [1]
              Text is not SVG - cannot display
              \ No newline at end of file +
              kind: Issuer
              metadata:
                name: issuer-1
              spec: ...
              kind: Issuer...
              ingress-shim creates the Certificate "cert-1"
              ingress-shim creates the Certificat...
              kind: Certificate
              metadata:
                name: cert-1
              spec:
                dnsNames:
                  - example.com
                  - foo.example.com
                issuerRef: issuer-1
                secretName: cert-1
              kind: Certificate...
              DOES NOT EXIST YET
              DOES NOT EXI...
              CERT-MANAGER ISSUER
              CERT-MANAGER ISSUER
              kind: Ingress
              metadata:
                name: ingress-1
                annotations:
                  cert-manager.io/issuer: issuer-1
              tls:
                - hosts:
                  - example.com
                  - foo.example.com
                  secretName: cert-1
              kind: Ingress...
              user creates an Ingress "ingress-1" with cert-manager annotations
              user creates an Ingress "ingress...
              kind: Secret
              metadata:
                name: cert-1
              stringData:
                tls.crt: |
                  -----BEGIN CERTIFICATE-----
                  (leaf)
                  -----END CERTIFICATE-----
                  -----BEGIN CERTIFICATE-----
                  (intermediate)
                  -----END CERTIFICATE-----

                tls.key: |
                  -----BEGIN PRIVATE KEY-----
                  AaBbCcDd0
                  -----END PRIVATE KEY-----
              kind: Secret...
              kind: Certificate
              spec:
                issuerRef: issuer-1
                secretName: cert-1
              status:
                revision: 1
                conditions:
                  - type: Ready
                    status: "True"
                    reason: Issued
                  - type: Issuing
                    status: "False"
                    reason: Issued
              kind: Certificate...
              Certificate Flow [1]
              Certificate Flow [1]
              Text is not SVG - cannot display
              \ No newline at end of file From 75a50a0427c911c3e495cbb926a015d0d6e822ee Mon Sep 17 00:00:00 2001 From: Peter Fiddes Date: Tue, 17 Oct 2023 16:06:47 +0100 Subject: [PATCH 197/264] fix: Docs needed Camel case feature gate names Signed-off-by: Peter Fiddes --- .../installation/configuring-components.md | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/content/docs/installation/configuring-components.md b/content/docs/installation/configuring-components.md index 061c982b74..68e89147c1 100644 --- a/content/docs/installation/configuring-components.md +++ b/content/docs/installation/configuring-components.md @@ -27,7 +27,8 @@ configuration file can be specified in the `config` and `webhook.config` values. The webhook configuration API documentation can be found on the [ControllerConfiguration](../reference/api-docs.md#controller.config.cert-manager.io/v1alpha1.ControllerConfiguration) page. -This is an example configuration file for the controller: +This is an example configuration file for the controller component: + ```yaml apiVersion: controller.config.cert-manager.io/v1alpha1 kind: ControllerConfiguration @@ -45,19 +46,22 @@ kubernetesAPIBurst: 50 numberOfConcurrentWorkers: 200 featureGates: - additionalCertificateOutputFormats: true - experimentalCertificateSigningRequestControllers: true - experimentalGatewayAPISupport: true - serverSideApply: true - literalCertificateSubject: true - useCertificateRequestBasicConstraints: true + AdditionalCertificateOutputFormats: true + ExperimentalCertificateSigningRequestControllers: true + ExperimentalGatewayAPISupport: true + ServerSideApply: true + LiteralCertificateSubject: true + UseCertificateRequestBasicConstraints: true ``` +> NOTE: This is included as an example only and not intended to be used as default settings. + ### Webhook configuration file The webhook configuration API documentation can be found on the [WebhookConfiguration](../reference/api-docs.md#webhook.config.cert-manager.io/v1alpha1.WebhookConfiguration) page. -This is an example configuration file for the webhook: +Here is an example configuration file for the webhook component: + ```yaml apiVersion: webhook.config.cert-manager.io/v1alpha1 kind: WebhookConfiguration @@ -70,10 +74,12 @@ securePort: 6443 healthzPort: 6080 featureGates: - additionalCertificateOutputFormats: true - literalCertificateSubject: true + AdditionalCertificateOutputFormats: true + LiteralCertificateSubject: true ``` +> NOTE: This is included as an example only and not intended to be used as default settings. + ## Feature gates Feature gates can be used to enable or disable experimental features in cert-manager. From f0af5f63281a4a10b4286df86c05a4d84a9d8863 Mon Sep 17 00:00:00 2001 From: Peter Fiddes Date: Tue, 17 Oct 2023 16:13:51 +0100 Subject: [PATCH 198/264] docs: Bold and case correction Signed-off-by: Peter Fiddes --- content/docs/installation/configuring-components.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/installation/configuring-components.md b/content/docs/installation/configuring-components.md index 68e89147c1..d10ba2d555 100644 --- a/content/docs/installation/configuring-components.md +++ b/content/docs/installation/configuring-components.md @@ -54,7 +54,7 @@ featureGates: UseCertificateRequestBasicConstraints: true ``` -> NOTE: This is included as an example only and not intended to be used as default settings. +> **Note:** This is included as an example only and not intended to be used as default settings. ### Webhook configuration file @@ -78,7 +78,7 @@ featureGates: LiteralCertificateSubject: true ``` -> NOTE: This is included as an example only and not intended to be used as default settings. +> **Note:** This is included as an example only and not intended to be used as default settings. ## Feature gates From a28df9e23d53140beda6f4d8645eafde4d4b9ac5 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 13 Oct 2023 18:20:29 +0200 Subject: [PATCH 199/264] reorganise 'Certificate resource' page Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/usage/certificate.md | 284 ++++++++++++++++-------------- 1 file changed, 147 insertions(+), 137 deletions(-) diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index 3882fba3c3..9b0bf9b4f3 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -6,10 +6,14 @@ description: 'cert-manager usage: Certificates' > **apiVersion:** cert-manager.io/v1 > **kind:** Certificate -In cert-manager, the [`Certificate`](../concepts/certificate.md) resource -represents a human readable definition of a certificate request that is to be -honored by an issuer which is to be kept up-to-date. This is the usual way that -you will interact with cert-manager to request signed certificates. +In cert-manager, the `Certificate` resource represents a human readable definition +of a certificate request. cert-manager uses this input to generate a private key +and [`CertificateRequest`](./certificaterequest.md) resource in order to obtain +a signed certificate from an [`Issuer`](../configuration/README.md) or +[`ClusterIssuer`](../configuration/README.md). The signed certificate and private +key are then stored in the specified `Secret` resource. cert-manager will ensure +that the certificate is [auto-renewed before it expires](#renewal-reissuance) and +[re-issued if requested](#non-renewal-reissuance). In order to issue any certificates, you'll need to configure an [`Issuer`](../configuration/README.md) or [`ClusterIssuer`](../configuration/README.md) @@ -121,7 +125,7 @@ A full list of the fields supported on the Certificate resource can be found in the [API reference documentation](../reference/api-docs.md#cert-manager.io/v1.CertificateSpec). -## X.509 key usages and extended key usages +### X.509 key usages and extended key usages cert-manager supports requesting certificates that have a number of [custom key usages](https://tools.ietf.org/html/rfc5280#section-4.2.1.3) and [extended key @@ -138,8 +142,141 @@ certificate does not match the current key usage set. An exhaustive list of supported key usages can be found in the [API reference documentation](../reference/api-docs.md#cert-manager.io/v1.KeyUsage). + +### Additional Certificate Output Formats + +
              + +⛔️ The additional certificate output formats feature is currently in an +_experimental_ alpha state, and is subject to breaking changes or complete +removal in future releases. This feature is only enabled by adding it to the +`--feature-gates` flag on the cert-manager controller and webhook components: + +```bash +--feature-gates=AdditionalCertificateOutputFormats=true +``` + +
              + +`additionalOutputFormats` is a field on the Certificate `spec` that allows +specifying additional supplementary formats of issued certificates and their +private key. There are currently two supported additional output formats: +`CombinedPEM` and `DER`. Both output formats can be specified on the same +Certificate. + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +spec: + ... + secretName: my-cert-tls + additionalOutputFormats: + - type: CombinedPEM + - type: DER + +# Results in: + +apiVersion: v1 +kind: Secret +metadata: + name: my-cert-tls +type: kubernetes.io/tls +data: + ca.crt: + tls.key: + tls.crt: + tls-combined.pem: + key.der: +``` + +#### `CombinedPEM` + +The `CombinedPEM` type will create a new key entry in the resulting +Certificate's Secret `tls-combined.pem`. This entry will contain the PEM encoded +private key, followed by at least one new line character, followed by the PEM +encoded signed certificate chain- + +```text + + "\n" + +``` + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-cert-tls +type: kubernetes.io/tls +data: + tls-combined.pem: + ... +``` + +#### `DER` + +The `DER` type will create a new key entry in the resulting Certificate's Secret +`key.der`. This entry will contain the DER binary format of the private key. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-cert-tls +type: kubernetes.io/tls +data: + key.der: + ... +``` + +## Issuance triggers + + +### Renewal-triggered reissuance + +cert-manager will automatically renew `Certificate`s. It will calculate _when_ to renew a `Certificate` based on the issued X.509 certificate's duration and a 'renewBefore' value which specifies _how long_ before expiry a certificate should be renewed. + +`spec.duration` and `spec.renewBefore` fields on a `Certificate` can be used to specify an X.509 certificate's duration and a 'renewBefore' value. Default value for `spec.duration` is 90 days. Some issuers might be configured to only issue certificates with a set duration, so the actual duration may be different. +Minimum value for `spec.duration` is 1 hour and minimum value for `spec.renewBefore` is 5 minutes. +It is also required that `spec.duration` > `spec.renewBefore`. + +Once an X.509 certificate has been issued, cert-manager will calculate the renewal time for the `Certificate`. By default this will be 2/3 through the X.509 certificate's duration. If `spec.renewBefore` has been set, it will be `spec.renewBefore` amount of time before expiry. cert-manager will set `Certificate`'s `status.RenewalTime` to the time when the renewal will be attempted. + + + +### Non-renewal-triggered reissuance + +Setting the `rotationPolicy: Always` won't rotate the private key immediately. +In order to rotate the private key, the certificate objects must be reissued. A +certificate object is reissued under the following circumstances: + +- when the X.509 certificate is nearing expiry, which is when the Certificate's + `status.renewalTime` is reached; +- when a change is made to one of the following fields on the Certificate's + spec: `commonName`, `dnsNames`, `ipAddresses`, `uris`, `emailAddresses`, + `subject`, `isCA`, `usages`, `duration` or `issuerRef`; +- when a reissuance is manually triggered with the following: + ```sh + cmctl renew cert-1 + ``` + Note that the above command requires [cmctl](../reference/cmctl.md#renew). + +
              + +**❌** Deleting the Secret resource associated with a Certificate resource is +**not a recommended solution** for manually rotating the private key. The +recommended way to manually rotate the private key is to trigger the reissuance +of the Certificate resource with the following command (requires +[`cmctl`](../reference/cmctl.md#renew)): + +```sh +cmctl renew cert-1 +``` + +
              + +## Issuance behavior + -## Temporary Certificates while Issuing +### Temporary Certificates while Issuing When requesting certificates [using the ingress-shim](./ingress.md), the component `ingress-gce`, if used, requires that a temporary certificate is @@ -161,13 +298,12 @@ Adding the following annotation on an ingress will automatically set "issue-temp ``` -## Rotation of the private key +### Rotation of the private key By default, the private key won't be rotated automatically. Using the setting `rotationPolicy: Always`, the private key Secret associated with a Certificate -object can be configured to be rotated as soon as an action triggers the -reissuance of the Certificate object (see -[Actions that will trigger a rotation of the private key](#actions-triggering-private-key-rotation) below). +object can be configured to be rotated as soon as an the Certificate is reissued (see +[Issuance triggers](#issuance-triggers)). With `rotationPolicy: Always`, cert-manager waits until the Certificate object is correctly signed before overwriting the `tls.key` file in the @@ -207,39 +343,7 @@ spec: rotationPolicy: Always # 🔰 Here. ``` - -### Actions that will trigger a rotation of the private key - -Setting the `rotationPolicy: Always` won't rotate the private key immediately. -In order to rotate the private key, the certificate objects must be reissued. A -certificate object is reissued under the following circumstances: - -- when the X.509 certificate is nearing expiry, which is when the Certificate's - `status.renewalTime` is reached; -- when a change is made to one of the following fields on the Certificate's - spec: `commonName`, `dnsNames`, `ipAddresses`, `uris`, `emailAddresses`, - `subject`, `isCA`, `usages`, `duration` or `issuerRef`; -- when a reissuance is manually triggered with the following: - ```sh - cmctl renew cert-1 - ``` - Note that the above command requires [cmctl](../reference/cmctl.md#renew). - -
              - -**❌** Deleting the Secret resource associated with a Certificate resource is -**not a recommended solution** for manually rotating the private key. The -recommended way to manually rotate the private key is to trigger the reissuance -of the Certificate resource with the following command (requires -[`cmctl`](../reference/cmctl.md#renew)): - -```sh -cmctl renew cert-1 -``` - -
              - -### The `rotationPolicy` setting +#### The `rotationPolicy` setting The possible values for `rotationPolicy` are: @@ -280,100 +384,6 @@ The `Secret` needs to be manually deleted if it is no longer needed. If you would prefer the `Secret` to be deleted automatically when the `Certificate` is deleted, you need to configure your installation to pass the `--enable-certificate-owner-ref` flag to the controller. -## Renewal - -cert-manager will automatically renew `Certificate`s. It will calculate _when_ to renew a `Certificate` based on the issued X.509 certificate's duration and a 'renewBefore' value which specifies _how long_ before expiry a certificate should be renewed. - -`spec.duration` and `spec.renewBefore` fields on a `Certificate` can be used to specify an X.509 certificate's duration and a 'renewBefore' value. Default value for `spec.duration` is 90 days. Some issuers might be configured to only issue certificates with a set duration, so the actual duration may be different. -Minimum value for `spec.duration` is 1 hour and minimum value for `spec.renewBefore` is 5 minutes. -It is also required that `spec.duration` > `spec.renewBefore`. - -Once an X.509 certificate has been issued, cert-manager will calculate the renewal time for the `Certificate`. By default this will be 2/3 through the X.509 certificate's duration. If `spec.renewBefore` has been set, it will be `spec.renewBefore` amount of time before expiry. cert-manager will set `Certificate`'s `status.RenewalTime` to the time when the renewal will be attempted. - -## Additional Certificate Output Formats - -
              - -⛔️ The additional certificate output formats feature is currently in an -_experimental_ alpha state, and is subject to breaking changes or complete -removal in future releases. This feature is only enabled by adding it to the -`--feature-gates` flag on the cert-manager controller and webhook components: - -```bash ---feature-gates=AdditionalCertificateOutputFormats=true -``` - -
              - -`additionalOutputFormats` is a field on the Certificate `spec` that allows -specifying additional supplementary formats of issued certificates and their -private key. There are currently two supported additional output formats: -`CombinedPEM` and `DER`. Both output formats can be specified on the same -Certificate. - -```yaml -apiVersion: cert-manager.io/v1 -kind: Certificate -spec: - ... - secretName: my-cert-tls - additionalOutputFormats: - - type: CombinedPEM - - type: DER - -# Results in: - -apiVersion: v1 -kind: Secret -metadata: - name: my-cert-tls -type: kubernetes.io/tls -data: - ca.crt: - tls.key: - tls.crt: - tls-combined.pem: - key.der: -``` - -#### `CombinedPEM` - -The `CombinedPEM` type will create a new key entry in the resulting -Certificate's Secret `tls-combined.pem`. This entry will contain the PEM encoded -private key, followed by at least one new line character, followed by the PEM -encoded signed certificate chain- - -```text - + "\n" + -``` - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: my-cert-tls -type: kubernetes.io/tls -data: - tls-combined.pem: - ... -``` - -#### `DER` - -The `DER` type will create a new key entry in the resulting Certificate's Secret -`key.der`. This entry will contain the DER binary format of the private key. - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: my-cert-tls -type: kubernetes.io/tls -data: - key.der: - ... -``` - ## Inner workings diagram for developers From 5c342f44aef13f1e32bbb0df3944c2378f152576 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:29:33 +0200 Subject: [PATCH 200/264] improve reissuance titles & content Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/usage/certificate.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index 9b0bf9b4f3..fae7d74ceb 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -230,7 +230,7 @@ data: ## Issuance triggers -### Renewal-triggered reissuance +### Reissuance triggered by expiry (renewal) cert-manager will automatically renew `Certificate`s. It will calculate _when_ to renew a `Certificate` based on the issued X.509 certificate's duration and a 'renewBefore' value which specifies _how long_ before expiry a certificate should be renewed. @@ -242,14 +242,10 @@ Once an X.509 certificate has been issued, cert-manager will calculate the renew -### Non-renewal-triggered reissuance +### Reissuance triggered by user actions -Setting the `rotationPolicy: Always` won't rotate the private key immediately. -In order to rotate the private key, the certificate objects must be reissued. A -certificate object is reissued under the following circumstances: +A certificate object is reissued under the following circumstances: -- when the X.509 certificate is nearing expiry, which is when the Certificate's - `status.renewalTime` is reached; - when a change is made to one of the following fields on the Certificate's spec: `commonName`, `dnsNames`, `ipAddresses`, `uris`, `emailAddresses`, `subject`, `isCA`, `usages`, `duration` or `issuerRef`; From 69ec3160c0aa521bf87dfc92e820323c69f47a65 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 18 Oct 2023 12:14:42 +0100 Subject: [PATCH 201/264] Write about high availability and scaling of cert-manager components Signed-off-by: Richard Wall --- .spelling | 1 + content/docs/installation/best-practice.md | 168 ++++++++++++++++++ .../best-practice/values.best-practice.yaml | 64 ++++++- 3 files changed, 232 insertions(+), 1 deletion(-) diff --git a/.spelling b/.spelling index a5fc61ca10..e4fcf161b4 100644 --- a/.spelling +++ b/.spelling @@ -532,6 +532,7 @@ mfmbarros maelvls bitscuit zsh +PodDisruptionBudget PodSecurityPolicy ClusterIP NodePort diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 2db8f3b153..d8b7473c65 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -22,6 +22,174 @@ are designed for backwards compatibility rather than for best practice or maximu You may find that the default resources do not comply with the security policy on your Kubernetes cluster and in that case you can modify the installation configuration using Helm chart values to override the defaults. +## High Availability + +cert-manager has three long-running components: controller, cainjector, and webhook. +Each of these components has a Deployment and by default each Deployment has 1 replica +but this does not provide high availability. +The Helm chart for cert-manager has parameters to configure the `replicaCount` for each Deployment, +and in production you should use 2 or more replicas to achieve high availability. + +### controller and cainjector + +The controller and cainjector components use [leader election](https://pkg.go.dev/k8s.io/client-go/tools/leaderelection) +to ensure that only one replica is active. +This prevents conflicts which would arise if multiple replicas were reconciling the same API resources. +So in these components you can use multiple replicas to achieve high availability but not for horizontal scaling. + +Use two replicas to ensures that there is a standby Pod scheduled to a Node which is ready to take leadership, +should the current leader encounter a disruption. +For example, if the Node on which the leader Pod is running is drained. +Or, if the leader Pod encounters an unexpected deadlock. + +There is little justification for using more than 2 replicas of these components. +Further replicas *may* add a degree of resilience +if you have the luxury of sufficient Nodes +with sufficient CPU and memory to accommodate the standby replicas. + +### webhook + +By default the cert-manager webhook Deployment has 1 replica, but in production you should use 3 or more. +If the cert-manager webhook is unavailable, all API operations on cert-manager custom resources will fail, +and this will disrupt any software that creates, updates or deletes cert-manager custom resources. +So it is *especially* important to keep at least one replica of the cert-manager webhook running at all times. + +> ℹ️ By contrast, if there is only a single replica of the cert-manager controller, there is less risk of disruption. +> For example, if the Node hosting the single cert-manager controller manager Pod is drained, +> there will be a delay while a new Pod is started on another Node, +> and any cert-manager resources that are created or changed during that time will not be reconciled until the new Pod starts up. +> But the controller manager works asynchronously anyway, so any applications which depend on the cert-manager custom resources +> will be designed to tolerate this situation. +> That being said, the best practice is to run 2 or more replicas of each controller if the cluster has sufficient resources. + +### Topology Spread Constraints + +Ensure that a disruption of a node or data center does not degrade the operation of cert-manager, +by configuring [Topology Spread Constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) +for each of the components. +For example, the following Helm chart values add topology spread constraints for all three long-running components, +to request (but not require) Kubernetes to avoid scheduling Pods of the same Deployment to the same zone or node. + +```yaml +topologySpreadConstraints: +- maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: controller +- maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: controller + +webhook: + replicaCount: 3 + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: webhook + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: webhook + +cainjector: + replicaCount: 2 + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: cainjector + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: cainjector +``` + +### PodDisruptionBudget + +For high availability you should also deploy a `PodDisruptionBudget` resource, +with `minAvailable=1` *or* with `maxUnavailable=1`. +This ensures that a *voluntary* disruption, such as the draining of a Node, can not proceed +until at least one other replica has been successfully scheduled and started on another Node. +The Helm chart has parameters to enable and configure a PodDisruptionBudget +for each of the long-running cert-manager components. + + + +## Scalability + +cert-manager has three long-running components: controller, cainjector, and webhook. +The Helm chart does not include resource requests and limits for any of these, +so you should supply resource requests and limits which are appropriate for your cluster. + +### controller and cainjector + +The controller and cainjector components use leader election to ensure that only one replica is active. +This prevents conflicts which would arise if multiple replicas were reconciling the same API resources. +You can not use horizontal scaling for these components. +Use vertical scaling instead. + +#### Memory + +Use vertical scaling to assign sufficient memory resources to these components. +The memory requirements will be higher on clusters with very many API resources or with large API resources. +This is because each of the components reconciles one or more Kubernetes API resources, +and each component will cache the metadata and sometimes the entire resource in memory, +so as to reduce the load on the Kubernetes API server. + +For example, if the cluster contains very many CertificateRequest resources, +you will need to increase the memory limit of the controller Pod. + +#### CPU + +Use vertical scaling to assign sufficient CPU resources to the these components. +The CPU requirements will be higher on clusters where there are very frequent updates to the resources which are reconciled by these components. +Whenever a resource changes, it will be queued to be re-reconciled by the component. +Higher CPU resources allow the component to process the queue faster. + +#### Workers + +Each of these component spawns a pool of worker threads to reconcile the queue of API resources. +You may need to increase this if you increase the CPU resources, +so that each component can make efficient use of the CPU cores assigned to it. + +By default the controller [uses 5 workers per controller](https://github.com/cert-manager/cert-manager/blob/3b0a5cec4140e92ba12f3eace362a4bd65fbb30e/cmd/controller/app/options/options.go#L180-L181) +which should be adequate for most clusters. +You can change the number of workers using the `--concurrent-workers` [flag of the controller manager](../cli/controller.md). + +The cainjector uses a single worker for each of the resource types that it reconciles, + +> ⚠️ This this is not yet configurable. + +### webhook + +The cert-manager webhook does not use leader election, so you *can* scale it horizontally by increasing the number of replicas. +When the Kubernetes API server connects to the cert-manager webhook it does so via a Service which load balances the connections +between all the Ready replicas. +For this reason, there is a clear benefit to increasing the number of webhook replicas to 3 or more, +on clusters where there is a high frequency of cert-manager custom resource interactions. +Furthermore, the webhook has modest memory requirements because it does not use a cache. +For this reason, the resource cost of scaling out the webhook is relatively low. + ## Use Liveness Probes An example of this recommendation is found in the Datree Documentation: diff --git a/public/docs/installation/best-practice/values.best-practice.yaml b/public/docs/installation/best-practice/values.best-practice.yaml index a6bec7a8be..5fc3d4d69e 100644 --- a/public/docs/installation/best-practice/values.best-practice.yaml +++ b/public/docs/installation/best-practice/values.best-practice.yaml @@ -1,5 +1,29 @@ # Helm chart values which make cert-manager comply with CIS, BSI and NSA -# security benchmarks. +# security benchmarks and other best practices for deploying cert-manager in +# production. +# +# Read the rationale for these values in: +# * https://cert-manager.io/docs/installation/best-practice/ + +replicaCount: 2 +topologySpreadConstraints: +- maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: controller +- maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: controller +podDisruptionBudget: + enabled: true + minAvailable: 1 automountServiceAccountToken: false serviceAccount: automountServiceAccountToken: false @@ -28,6 +52,25 @@ volumeMounts: readOnly: true webhook: + replicaCount: 3 + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: webhook + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: webhook + podDisruptionBudget: + enabled: true + minAvailable: 1 automountServiceAccountToken: false serviceAccount: automountServiceAccountToken: false @@ -56,6 +99,25 @@ webhook: readOnly: true cainjector: + replicaCount: 2 + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: cainjector + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: cainjector + podDisruptionBudget: + enabled: true + minAvailable: 1 automountServiceAccountToken: false serviceAccount: automountServiceAccountToken: false From 194d0ca9f3242262e9751d4f875522ed75828ebc Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 18 Oct 2023 16:56:05 +0100 Subject: [PATCH 202/264] Apply suggestions from code review Thanks @jsoref for correcting these mistakes Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index d8b7473c65..8ee7c47f91 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -39,13 +39,13 @@ So in these components you can use multiple replicas to achieve high availabilit Use two replicas to ensures that there is a standby Pod scheduled to a Node which is ready to take leadership, should the current leader encounter a disruption. -For example, if the Node on which the leader Pod is running is drained. +For example, when the leader Pod is drained from its node. Or, if the leader Pod encounters an unexpected deadlock. There is little justification for using more than 2 replicas of these components. Further replicas *may* add a degree of resilience if you have the luxury of sufficient Nodes -with sufficient CPU and memory to accommodate the standby replicas. +with sufficient CPU and memory to accommodate additional standby replicas. ### webhook @@ -128,7 +128,7 @@ cainjector: For high availability you should also deploy a `PodDisruptionBudget` resource, with `minAvailable=1` *or* with `maxUnavailable=1`. -This ensures that a *voluntary* disruption, such as the draining of a Node, can not proceed +This ensures that a *voluntary* disruption, such as the draining of a Node, cannot proceed until at least one other replica has been successfully scheduled and started on another Node. The Helm chart has parameters to enable and configure a PodDisruptionBudget for each of the long-running cert-manager components. @@ -145,7 +145,7 @@ so you should supply resource requests and limits which are appropriate for your The controller and cainjector components use leader election to ensure that only one replica is active. This prevents conflicts which would arise if multiple replicas were reconciling the same API resources. -You can not use horizontal scaling for these components. +You cannot use horizontal scaling for these components. Use vertical scaling instead. #### Memory From cfe144dcdbbeb1e5bfa71630d8517467926f7d1a Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 18 Oct 2023 16:57:02 +0100 Subject: [PATCH 203/264] Update content/docs/installation/best-practice.md Thanks @hawksight. I agree. Co-authored-by: Peter Fiddes Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 8ee7c47f91..d67851bb76 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -156,7 +156,7 @@ This is because each of the components reconciles one or more Kubernetes API res and each component will cache the metadata and sometimes the entire resource in memory, so as to reduce the load on the Kubernetes API server. -For example, if the cluster contains very many CertificateRequest resources, +If your cluster contains a high volume of `CertificateRequest` resources such as when using many ephemeral or short lived certificates rotated frequently, you will need to increase the memory limit of the controller Pod. #### CPU From f3bacf3d490bc297941edf3ec095b764e857bcb0 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 18 Oct 2023 16:42:33 +0100 Subject: [PATCH 204/264] Remove mention of worker processes Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index d67851bb76..99e593b9d5 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -133,8 +133,6 @@ until at least one other replica has been successfully scheduled and started on The Helm chart has parameters to enable and configure a PodDisruptionBudget for each of the long-running cert-manager components. - - ## Scalability cert-manager has three long-running components: controller, cainjector, and webhook. @@ -166,20 +164,6 @@ The CPU requirements will be higher on clusters where there are very frequent up Whenever a resource changes, it will be queued to be re-reconciled by the component. Higher CPU resources allow the component to process the queue faster. -#### Workers - -Each of these component spawns a pool of worker threads to reconcile the queue of API resources. -You may need to increase this if you increase the CPU resources, -so that each component can make efficient use of the CPU cores assigned to it. - -By default the controller [uses 5 workers per controller](https://github.com/cert-manager/cert-manager/blob/3b0a5cec4140e92ba12f3eace362a4bd65fbb30e/cmd/controller/app/options/options.go#L180-L181) -which should be adequate for most clusters. -You can change the number of workers using the `--concurrent-workers` [flag of the controller manager](../cli/controller.md). - -The cainjector uses a single worker for each of the resource types that it reconciles, - -> ⚠️ This this is not yet configurable. - ### webhook The cert-manager webhook does not use leader election, so you *can* scale it horizontally by increasing the number of replicas. From a289f951eaa62c4235763c7bdb3a5a45fdd1cf98 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 18 Oct 2023 17:22:38 +0100 Subject: [PATCH 205/264] Add discrete Helm values examples for each recommendation. Link to Google GKE documentation which talks about webhook disruptions Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 38 ++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 99e593b9d5..5921d7ed52 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -27,8 +27,16 @@ and in that case you can modify the installation configuration using Helm chart cert-manager has three long-running components: controller, cainjector, and webhook. Each of these components has a Deployment and by default each Deployment has 1 replica but this does not provide high availability. -The Helm chart for cert-manager has parameters to configure the `replicaCount` for each Deployment, -and in production you should use 2 or more replicas to achieve high availability. +The Helm chart for cert-manager has parameters to configure the `replicaCount` for each Deployment. +In production we recommend the following `replicaCount` parameters: + +```yaml +replicaCount: 2 +webhook: + replicaCount: 3 +cainjector: + replicaCount: 2 +``` ### controller and cainjector @@ -51,8 +59,9 @@ with sufficient CPU and memory to accommodate additional standby replicas. By default the cert-manager webhook Deployment has 1 replica, but in production you should use 3 or more. If the cert-manager webhook is unavailable, all API operations on cert-manager custom resources will fail, -and this will disrupt any software that creates, updates or deletes cert-manager custom resources. -So it is *especially* important to keep at least one replica of the cert-manager webhook running at all times. +and this will disrupt any software that creates, updates or deletes cert-manager custom resources, +and it may cause other disruptions to your cluster. +So it is *especially* important to keep at multiple replicas of the cert-manager webhook running at all times. > ℹ️ By contrast, if there is only a single replica of the cert-manager controller, there is less risk of disruption. > For example, if the Node hosting the single cert-manager controller manager Pod is drained, @@ -61,6 +70,10 @@ So it is *especially* important to keep at least one replica of the cert-manager > But the controller manager works asynchronously anyway, so any applications which depend on the cert-manager custom resources > will be designed to tolerate this situation. > That being said, the best practice is to run 2 or more replicas of each controller if the cluster has sufficient resources. +> +> 📖 Read [Ensure control plane stability when using webhooks](https://cloud.google.com/kubernetes-engine/docs/how-to/optimize-webhooks) +> in the Google Kubernetes Engine (GKE) documentation, +> for examples of how webhook disruptions might disrupt your cluster. ### Topology Spread Constraints @@ -88,7 +101,6 @@ topologySpreadConstraints: app.kubernetes.io/component: controller webhook: - replicaCount: 3 topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone @@ -106,7 +118,6 @@ webhook: app.kubernetes.io/component: webhook cainjector: - replicaCount: 2 topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone @@ -132,6 +143,21 @@ This ensures that a *voluntary* disruption, such as the draining of a Node, cann until at least one other replica has been successfully scheduled and started on another Node. The Helm chart has parameters to enable and configure a PodDisruptionBudget for each of the long-running cert-manager components. +We recommend the following parameters: + +```yaml +podDisruptionBudget: + enabled: true + minAvailable: 1 +webhook: + podDisruptionBudget: + enabled: true + minAvailable: 1 +cainjector: + podDisruptionBudget: + enabled: true + minAvailable: 1 +``` ## Scalability From 5a8213232334e712c4f2043163ed75d905b063ff Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 18 Oct 2023 17:45:16 +0100 Subject: [PATCH 206/264] Explain when and how to limit the memory usage of cainjector Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 17 +++++++++++++++++ .../best-practice/values.best-practice.yaml | 3 +++ 2 files changed, 20 insertions(+) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 5921d7ed52..769809f0cd 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -183,6 +183,23 @@ so as to reduce the load on the Kubernetes API server. If your cluster contains a high volume of `CertificateRequest` resources such as when using many ephemeral or short lived certificates rotated frequently, you will need to increase the memory limit of the controller Pod. +You can also reduce the memory consumption of `cainjector` +by configuring it to only watch resources in the `cert-manager` namespace, +and by configuring it to **not** watch `Certificate` resources. +Here's how to configure the [cainjector command line flags](../cli/cainjector.md) using Helm chart values: + +```yaml +cainjector: + extraArgs: + - --namespace=cert-manager + - --enable-certificates-data-source=false +``` + +> ⚠️️ This optimization is only appropriate if `cainjector` is being used exclusively for the the cert-manager webhook. +> It is not appropriate if `cainjector` is also being used to manage the TLS certificates for webhooks of other software. +> For example, some Kubebuilder derived projects may depend on `cainjector` +> to [inject TLS certificates for their webhooks](https://book.kubebuilder.io/cronjob-tutorial/running-webhook.html#cert-manager). + #### CPU Use vertical scaling to assign sufficient CPU resources to the these components. diff --git a/public/docs/installation/best-practice/values.best-practice.yaml b/public/docs/installation/best-practice/values.best-practice.yaml index 5fc3d4d69e..41c8a8278b 100644 --- a/public/docs/installation/best-practice/values.best-practice.yaml +++ b/public/docs/installation/best-practice/values.best-practice.yaml @@ -99,6 +99,9 @@ webhook: readOnly: true cainjector: + extraArgs: + - --namespace=cert-manager + - --enable-certificates-data-source=false replicaCount: 2 topologySpreadConstraints: - maxSkew: 1 From 5973793ae1894d8d6b8d53211a4e8da92f71062c Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 20 Oct 2023 14:05:32 +0100 Subject: [PATCH 207/264] Explain that the default topology spread constraints are probably adequate Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 75 ++++++------------- .../best-practice/values.best-practice.yaml | 45 ----------- 2 files changed, 21 insertions(+), 99 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 769809f0cd..eb56de16d9 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -77,63 +77,30 @@ So it is *especially* important to keep at multiple replicas of the cert-manager ### Topology Spread Constraints -Ensure that a disruption of a node or data center does not degrade the operation of cert-manager, -by configuring [Topology Spread Constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) -for each of the components. -For example, the following Helm chart values add topology spread constraints for all three long-running components, -to request (but not require) Kubernetes to avoid scheduling Pods of the same Deployment to the same zone or node. +Consider using [Topology Spread Constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/), +to ensure that a disruption of a node or data center does not degrade the operation of cert-manager. -```yaml -topologySpreadConstraints: -- maxSkew: 1 - topologyKey: topology.kubernetes.io/zone - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: controller -- maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: controller +For high availability you do not want the replica Pods to be scheduled on the same Node, +because if that node fails, both the active and standby Pods will exit, +and there will be no further reconciliation of the resources by that controller, +until there is another Node with sufficient free resources to run a new Pod, +and until that Pod has become Ready. -webhook: - topologySpreadConstraints: - - maxSkew: 1 - topologyKey: topology.kubernetes.io/zone - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: webhook - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: webhook +It is also desirable for the Pods to be running in separate data centers (availability zones), +if the cluster has nodes distributed between zones. +Then, in the event of a failure at the data center hosting the active Pod , +the standby Pod will immediately be available to take leadership. -cainjector: - topologySpreadConstraints: - - maxSkew: 1 - topologyKey: topology.kubernetes.io/zone - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: cainjector - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: cainjector -``` +Fortunately you may not need to do anything to achieve these goals +because [Kubernetes >= 1.24 has Built-in default constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/#internal-default-constraints) +which should mean that the high availablity scheduling described above will happen implicitly. + +> ℹ️ In case your cluster does not use Built-in default constraints. +> +> You can add [Topology Spread Constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) +> to each of the cert-manager components using Helm chart values. +> For example, the following Helm chart values add topology spread constraints for all three long-running components, +> to request (but not require) Kubernetes to avoid scheduling Pods of the same Deployment to the same zone or node. ### PodDisruptionBudget diff --git a/public/docs/installation/best-practice/values.best-practice.yaml b/public/docs/installation/best-practice/values.best-practice.yaml index 41c8a8278b..7a54511872 100644 --- a/public/docs/installation/best-practice/values.best-practice.yaml +++ b/public/docs/installation/best-practice/values.best-practice.yaml @@ -6,21 +6,6 @@ # * https://cert-manager.io/docs/installation/best-practice/ replicaCount: 2 -topologySpreadConstraints: -- maxSkew: 1 - topologyKey: topology.kubernetes.io/zone - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: controller -- maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: controller podDisruptionBudget: enabled: true minAvailable: 1 @@ -53,21 +38,6 @@ volumeMounts: webhook: replicaCount: 3 - topologySpreadConstraints: - - maxSkew: 1 - topologyKey: topology.kubernetes.io/zone - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: webhook - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: webhook podDisruptionBudget: enabled: true minAvailable: 1 @@ -103,21 +73,6 @@ cainjector: - --namespace=cert-manager - --enable-certificates-data-source=false replicaCount: 2 - topologySpreadConstraints: - - maxSkew: 1 - topologyKey: topology.kubernetes.io/zone - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: cainjector - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: ScheduleAnyway - labelSelector: - matchLabels: - app.kubernetes.io/instance: cert-manager - app.kubernetes.io/component: cainjector podDisruptionBudget: enabled: true minAvailable: 1 From a0504de59a820a80206632fa8eb398d213e3a0ec Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 20 Oct 2023 14:46:58 +0100 Subject: [PATCH 208/264] Update content/docs/installation/best-practice.md Co-authored-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index eb56de16d9..c5b072108f 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -59,7 +59,7 @@ with sufficient CPU and memory to accommodate additional standby replicas. By default the cert-manager webhook Deployment has 1 replica, but in production you should use 3 or more. If the cert-manager webhook is unavailable, all API operations on cert-manager custom resources will fail, -and this will disrupt any software that creates, updates or deletes cert-manager custom resources, +and this will disrupt any software that creates, updates or deletes cert-manager custom resources (including cert-manager itself), and it may cause other disruptions to your cluster. So it is *especially* important to keep at multiple replicas of the cert-manager webhook running at all times. From 303343a3e0a58c008654cab0b61bfdfc6d528398 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 20 Oct 2023 14:51:40 +0100 Subject: [PATCH 209/264] Update content/docs/installation/best-practice.md Co-authored-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index c5b072108f..719277fc28 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -51,9 +51,6 @@ For example, when the leader Pod is drained from its node. Or, if the leader Pod encounters an unexpected deadlock. There is little justification for using more than 2 replicas of these components. -Further replicas *may* add a degree of resilience -if you have the luxury of sufficient Nodes -with sufficient CPU and memory to accommodate additional standby replicas. ### webhook From 9b283a0fa2bb5dd3fbfacf634af0498e6803d65a Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 20 Oct 2023 14:21:02 +0100 Subject: [PATCH 210/264] Fix typo Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 719277fc28..954b0bd9e2 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -90,7 +90,7 @@ the standby Pod will immediately be available to take leadership. Fortunately you may not need to do anything to achieve these goals because [Kubernetes >= 1.24 has Built-in default constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/#internal-default-constraints) -which should mean that the high availablity scheduling described above will happen implicitly. +which should mean that the high availability scheduling described above will happen implicitly. > ℹ️ In case your cluster does not use Built-in default constraints. > From 0366cad727b0b386559247497b6114782859ac93 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 20 Oct 2023 14:33:04 +0100 Subject: [PATCH 211/264] Further reading and warnings around the use of Pod Disruption Budget Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 954b0bd9e2..32d458014a 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -101,8 +101,8 @@ which should mean that the high availability scheduling described above will hap ### PodDisruptionBudget -For high availability you should also deploy a `PodDisruptionBudget` resource, -with `minAvailable=1` *or* with `maxUnavailable=1`. +For high availability you should also deploy a `PodDisruptionBudget` resource with `minAvailable=1`. + This ensures that a *voluntary* disruption, such as the draining of a Node, cannot proceed until at least one other replica has been successfully scheduled and started on another Node. The Helm chart has parameters to enable and configure a PodDisruptionBudget @@ -123,6 +123,12 @@ cainjector: minAvailable: 1 ``` +> 📖 Read about [Specifying a Disruption Budget for your Application](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) in the Kubernetes documentation. +> +> ⚠️ These PodDisruptionBudget settings are only suitable for high availability deployments. +> You must increase the `replicaCount` of each Deployment to more than the `minAvailable` value, +> otherwise the PodDisruptionBudget will prevent you from draining cert-manager Pods. + ## Scalability cert-manager has three long-running components: controller, cainjector, and webhook. From d05d333f592e4bea4bca26ef7785591ebbf8b15c Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 20 Oct 2023 15:21:37 +0100 Subject: [PATCH 212/264] Make this paragraph make sense Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 32d458014a..070810f720 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -93,11 +93,8 @@ because [Kubernetes >= 1.24 has Built-in default constraints](https://kubernetes which should mean that the high availability scheduling described above will happen implicitly. > ℹ️ In case your cluster does not use Built-in default constraints. -> > You can add [Topology Spread Constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) > to each of the cert-manager components using Helm chart values. -> For example, the following Helm chart values add topology spread constraints for all three long-running components, -> to request (but not require) Kubernetes to avoid scheduling Pods of the same Deployment to the same zone or node. ### PodDisruptionBudget From 51216003062bdf79e9a4dc124cb4aff820e5b8e0 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 20 Oct 2023 15:27:31 +0100 Subject: [PATCH 213/264] Link to The Darker Side of Webhooks blog post Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 070810f720..c59a16d842 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -71,6 +71,9 @@ So it is *especially* important to keep at multiple replicas of the cert-manager > 📖 Read [Ensure control plane stability when using webhooks](https://cloud.google.com/kubernetes-engine/docs/how-to/optimize-webhooks) > in the Google Kubernetes Engine (GKE) documentation, > for examples of how webhook disruptions might disrupt your cluster. +> +> 📖 Read [The dark side of Kubernetes admission webhooks](https://techblog.cisco.com/blog/dark-side-of-kubernetes-admission-webhooks) +> on the Cisco Tech Blog, to learn more about potential issues caused by webhooks and how you can avoid them. ### Topology Spread Constraints From faeaf0eef2e7452e705313e5ff4e62771904b39b Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 20 Oct 2023 17:14:45 +0100 Subject: [PATCH 214/264] Explain why and how to isolate the cert-manager workloads Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index c59a16d842..7ecc4daf2a 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -22,6 +22,109 @@ are designed for backwards compatibility rather than for best practice or maximu You may find that the default resources do not comply with the security policy on your Kubernetes cluster and in that case you can modify the installation configuration using Helm chart values to override the defaults. +## Isolate cert-manager on dedicated node pools + +cert-manager is a cluster scoped operator and you should treat it as part of your platform control plane. + +The cert-manager controller caches all the Secret resources of the cluster in memory, +so if an untrusted / malicious workload were to be scheduled to the same Node as the controller, +and somehow gain privileged access to the underlying node, +it may be able to read the secrets from memory. +You can mitigate this risk by running cert-manager on nodes that are reserved for trusted platform operators. + +This can be achieved using node taints and node affinity to schedule cert-manager Pods to Nodes +which are dedicated to running your platform components. +A node taint tells Kubernetes to avoid scheduling Pods without a corresponding toleration on those nodes. +The node affinity on Pods tells Kubernetes to schedule those Pods on the dedicated nodes. + +The Helm chart for cert-manager has parameters to configure the `tolerations` and `nodeAffinity` for each component. +The exact values of these parameters will depend on you particular cluster. +For example, if you have a pool of nodes +labelled with `kubectl label node ... node-restriction.kubernetes.io/reserved-for=platform` and +tainted with `kubectl taint node ... node-restriction.kubernetes.io/reserved-for=platform:NoExecute`, +you can add the following affinity and tolerations values to allow cert-manager Pods to run on those nodes: + +```yaml +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-restriction.kubernetes.io/reserved-for + operator: In + values: + - platform +tolerations: +- key: node-restriction.kubernetes.io/reserved-for + operator: Equal + value: platform + +webhook: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-restriction.kubernetes.io/reserved-for + operator: In + values: + - platform + tolerations: + - key: node-restriction.kubernetes.io/reserved-for + operator: Equal + value: platform + +cainjector: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-restriction.kubernetes.io/reserved-for + operator: In + values: + - platform + tolerations: + - key: node-restriction.kubernetes.io/reserved-for + operator: Equal + value: platform + +startupapicheck: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-restriction.kubernetes.io/reserved-for + operator: In + values: + - platform + tolerations: + - key: node-restriction.kubernetes.io/reserved-for + operator: Equal + value: platform +``` + +> 📖 Read more about [Taints and Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) +> in the Kubernetes documentation. +> +> 📖 Read the [Guide to isolating tenant workloads to specific nodes](https://aws.github.io/aws-eks-best-practices/security/docs/multitenancy/#isolating-tenant-workloads-to-specific-nodes) +> in the EKS Best Practice Guides, +> for an in-depth explanation of these techniques. +> +> 📖 Learn how to [Isolate your workloads in dedicated node pools](https://cloud.google.com/kubernetes-engine/docs/how-to/isolate-workloads-dedicated-nodes) on Google Kubernetes Engine. +> +> 📖 Read more about the [`node-restriction.kubernetes.io/` prefix and the `NodeRestriction` admission plugin](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction). +> +> ℹ️ On a multi-tenant cluster, +> consider enabling the [`PodTolerationRestriction` plugin](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#podtolerationrestriction) +> to limit which tolerations tenants may add to their Pods. +> You may also use that plugin to add default tolerations to the `cert-manager` namespace, +> which obviates the need to explicitly set the tolerations in the Helm chart. +> +> ℹ️ Alternatively, you could use Kyverno to limit which tolerations Pods are allowed to use. +> Read [Restrict control plane scheduling](https://kyverno.io/policies/other/res/restrict-controlplane-scheduling/restrict-controlplane-scheduling/) as a starting point. + ## High Availability cert-manager has three long-running components: controller, cainjector, and webhook. From ac18af6b568affc4e6f35b6cc5f41d5deb13356c Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Mon, 23 Oct 2023 12:42:22 +0100 Subject: [PATCH 215/264] Use nodeSelector instead of affinity, for simplicity Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 80 +++++++++------------- 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 7ecc4daf2a..86926259aa 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -25,86 +25,70 @@ and in that case you can modify the installation configuration using Helm chart ## Isolate cert-manager on dedicated node pools cert-manager is a cluster scoped operator and you should treat it as part of your platform control plane. +The cert-manager controller creates and modifies Kubernetes Secret resources +and the controller and cainjector both cache TLS Secret resources in memory. +These are two reasons why you should consider isolating the cert-manager components from +other less privileged workloads. +For example, if an untrusted or malicious workload runs on the same Node as the cert-manager controller, +and somehow gains root access to the underlying node, +it may be able to read the private keys in Secrets that the controller has cached in memory. -The cert-manager controller caches all the Secret resources of the cluster in memory, -so if an untrusted / malicious workload were to be scheduled to the same Node as the controller, -and somehow gain privileged access to the underlying node, -it may be able to read the secrets from memory. You can mitigate this risk by running cert-manager on nodes that are reserved for trusted platform operators. +This can be achieved using a combination of Node taints, Pod tolerations and Pod node selector settings. +* A Node `taint` tells the Kubernetes scheduler to *exclude* Pods from a Node, by default. +* A Pod `toleration` tells the Kubernetes scheduler to *allow* Pods on the tainted Node. +* A Pod `nodeSelector` tells the Kubernetes scheduler to *place* Pods on a Node with matching labels. -This can be achieved using node taints and node affinity to schedule cert-manager Pods to Nodes -which are dedicated to running your platform components. -A node taint tells Kubernetes to avoid scheduling Pods without a corresponding toleration on those nodes. -The node affinity on Pods tells Kubernetes to schedule those Pods on the dedicated nodes. - -The Helm chart for cert-manager has parameters to configure the `tolerations` and `nodeAffinity` for each component. -The exact values of these parameters will depend on you particular cluster. +The Helm chart for cert-manager has parameters to configure the Pod `tolerations` and `nodeSelector` for each component. +The exact values of these parameters will depend on your particular cluster. For example, if you have a pool of nodes labelled with `kubectl label node ... node-restriction.kubernetes.io/reserved-for=platform` and tainted with `kubectl taint node ... node-restriction.kubernetes.io/reserved-for=platform:NoExecute`, -you can add the following affinity and tolerations values to allow cert-manager Pods to run on those nodes: +you can use the following values to run cert-manager Pods on those nodes: ```yaml -affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-restriction.kubernetes.io/reserved-for - operator: In - values: - - platform +nodeSelector: + kubernetes.io/os: linux + node-restriction.kubernetes.io/reserved-for: platform tolerations: - key: node-restriction.kubernetes.io/reserved-for operator: Equal value: platform webhook: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-restriction.kubernetes.io/reserved-for - operator: In - values: - - platform + nodeSelector: + kubernetes.io/os: linux + node-restriction.kubernetes.io/reserved-for: platform tolerations: - key: node-restriction.kubernetes.io/reserved-for operator: Equal value: platform cainjector: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-restriction.kubernetes.io/reserved-for - operator: In - values: - - platform + nodeSelector: + kubernetes.io/os: linux + node-restriction.kubernetes.io/reserved-for: platform tolerations: - key: node-restriction.kubernetes.io/reserved-for operator: Equal value: platform startupapicheck: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-restriction.kubernetes.io/reserved-for - operator: In - values: - - platform + nodeSelector: + kubernetes.io/os: linux + node-restriction.kubernetes.io/reserved-for: platform tolerations: - key: node-restriction.kubernetes.io/reserved-for operator: Equal value: platform ``` +> ℹ️ This example uses `nodeSelector` to *place* the Pods but you could also use `affinity.nodeAffinity`. +> `nodeSelector` is chosen here because it has a simpler syntax. +> +> ℹ️ The default `nodeSelector` value `kubernetes.io/os: linux` [avoids placing cert-manager Pods on Windows nodes in a mixed OS cluster](https://github.com/cert-manager/cert-manager/pull/3605), +> so that must be explicitly included here too. +> > 📖 Read more about [Taints and Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) > in the Kubernetes documentation. > From 6fd4d3f98c9334fffdde484911543f4f764b6eaa Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Mon, 23 Oct 2023 12:52:08 +0100 Subject: [PATCH 216/264] Link to all the general documentation sites in addition to specific pages Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 86926259aa..3834bf9c53 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -24,7 +24,7 @@ and in that case you can modify the installation configuration using Helm chart ## Isolate cert-manager on dedicated node pools -cert-manager is a cluster scoped operator and you should treat it as part of your platform control plane. +cert-manager is a cluster scoped operator and you should treat it as part of your platform's control plane. The cert-manager controller creates and modifies Kubernetes Secret resources and the controller and cainjector both cache TLS Secret resources in memory. These are two reasons why you should consider isolating the cert-manager components from @@ -90,13 +90,13 @@ startupapicheck: > so that must be explicitly included here too. > > 📖 Read more about [Taints and Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) -> in the Kubernetes documentation. +> in the [Kubernetes documentation](https://kubernetes.io/docs/). > > 📖 Read the [Guide to isolating tenant workloads to specific nodes](https://aws.github.io/aws-eks-best-practices/security/docs/multitenancy/#isolating-tenant-workloads-to-specific-nodes) -> in the EKS Best Practice Guides, +> in the [EKS Best Practice Guides](https://aws.github.io/aws-eks-best-practices/), > for an in-depth explanation of these techniques. > -> 📖 Learn how to [Isolate your workloads in dedicated node pools](https://cloud.google.com/kubernetes-engine/docs/how-to/isolate-workloads-dedicated-nodes) on Google Kubernetes Engine. +> 📖 Learn how to [Isolate your workloads in dedicated node pools](https://cloud.google.com/kubernetes-engine/docs/how-to/isolate-workloads-dedicated-nodes) on [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/docs/). > > 📖 Read more about the [`node-restriction.kubernetes.io/` prefix and the `NodeRestriction` admission plugin](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction). > @@ -106,7 +106,7 @@ startupapicheck: > You may also use that plugin to add default tolerations to the `cert-manager` namespace, > which obviates the need to explicitly set the tolerations in the Helm chart. > -> ℹ️ Alternatively, you could use Kyverno to limit which tolerations Pods are allowed to use. +> ℹ️ Alternatively, you could use [Kyverno](https://kyverno.io/docs/) to limit which tolerations Pods are allowed to use. > Read [Restrict control plane scheduling](https://kyverno.io/policies/other/res/restrict-controlplane-scheduling/restrict-controlplane-scheduling/) as a starting point. ## High Availability From 4195d60ec0538ccf95de0ba429a2ed9cb6690726 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Mon, 23 Oct 2023 14:20:33 +0100 Subject: [PATCH 217/264] Update content/docs/installation/best-practice.md Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 3834bf9c53..87cac871ce 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -39,7 +39,7 @@ This can be achieved using a combination of Node taints, Pod tolerations and Pod * A Pod `toleration` tells the Kubernetes scheduler to *allow* Pods on the tainted Node. * A Pod `nodeSelector` tells the Kubernetes scheduler to *place* Pods on a Node with matching labels. -The Helm chart for cert-manager has parameters to configure the Pod `tolerations` and `nodeSelector` for each component. +The Helm chart for cert-manager has parameters to configure the Pod `tolerations` and `nodeSelector` for each component. The exact values of these parameters will depend on your particular cluster. For example, if you have a pool of nodes labelled with `kubectl label node ... node-restriction.kubernetes.io/reserved-for=platform` and From 0a539e57c55c631c2ab29af292e1090ab5f37017 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 24 Oct 2023 08:18:08 +0100 Subject: [PATCH 218/264] Make it clear that the use of taints and toleration is only an example Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 30 ++++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 87cac871ce..2d9d639353 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -34,17 +34,30 @@ and somehow gains root access to the underlying node, it may be able to read the private keys in Secrets that the controller has cached in memory. You can mitigate this risk by running cert-manager on nodes that are reserved for trusted platform operators. -This can be achieved using a combination of Node taints, Pod tolerations and Pod node selector settings. -* A Node `taint` tells the Kubernetes scheduler to *exclude* Pods from a Node, by default. -* A Pod `toleration` tells the Kubernetes scheduler to *allow* Pods on the tainted Node. -* A Pod `nodeSelector` tells the Kubernetes scheduler to *place* Pods on a Node with matching labels. The Helm chart for cert-manager has parameters to configure the Pod `tolerations` and `nodeSelector` for each component. The exact values of these parameters will depend on your particular cluster. -For example, if you have a pool of nodes -labelled with `kubectl label node ... node-restriction.kubernetes.io/reserved-for=platform` and -tainted with `kubectl taint node ... node-restriction.kubernetes.io/reserved-for=platform:NoExecute`, -you can use the following values to run cert-manager Pods on those nodes: + +### Example + +This example demonstrates how to use: +`taints` to *repel* non-platform Pods from Nodes which you have reserved for your platform's control-plane, +`tolerations` to *allow* cert-manager Pods to run on those Nodes, and +`nodeSelector` to *place* the cert-manager Pods on those Nodes. + +Label the Nodes: + +```bash +kubectl label node ... node-restriction.kubernetes.io/reserved-for=platform +``` + +Taint the Nodes: + +```bash +kubectl taint node ... node-restriction.kubernetes.io/reserved-for=platform:NoExecute +``` + +Then install cert-manager using the following Helm chart values: ```yaml nodeSelector: @@ -85,6 +98,7 @@ startupapicheck: > ℹ️ This example uses `nodeSelector` to *place* the Pods but you could also use `affinity.nodeAffinity`. > `nodeSelector` is chosen here because it has a simpler syntax. +> Read [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) to learn more. > > ℹ️ The default `nodeSelector` value `kubernetes.io/os: linux` [avoids placing cert-manager Pods on Windows nodes in a mixed OS cluster](https://github.com/cert-manager/cert-manager/pull/3605), > so that must be explicitly included here too. From b050c03d23b1eccf4877b92a89cfe61ff71a1b3d Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 24 Oct 2023 16:16:59 +0200 Subject: [PATCH 219/264] add redirect rules for the /docs/installation/upgrading/, /docs/releases/upgrading/ and /docs/releases/release-notes/ pages Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- public/_redirects | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/_redirects b/public/_redirects index 2d80cbc1ad..199af9c005 100644 --- a/public/_redirects +++ b/public/_redirects @@ -212,9 +212,12 @@ https://docs.cert-manager.io/* https://cert-manager.io/docs/:splat 302! /docs/installation/api-compatibility/ /docs/contributing/api-compatibility/ 301! # Moved the "upgrading" and "release-notes" pages to the release section +/docs/installation/upgrading/ /docs/installation/upgrade/ 301! /docs/installation/upgrading/* /docs/releases/upgrading/:splat 301! /docs/release-notes/* /docs/releases/release-notes/:splat 301! /docs/installation/supported-releases/ /docs/releases/ 301! +/docs/releases/upgrading/ /docs/releases/ 301! +/docs/releases/release-notes/ /docs/releases/ 301! # Moved the concept pages into the main website /docs/concepts/certificaterequest/ /docs/usage/certificaterequest/ 301! From a4e33a2ef18645d7d5291f1d21896c074ad36d4a Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Tue, 24 Oct 2023 16:21:53 +0200 Subject: [PATCH 220/264] update paragraph to point to new location of upgrade & release notes Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/installation/upgrade.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/content/docs/installation/upgrade.md b/content/docs/installation/upgrade.md index cc02cd1056..0bfc4da1e3 100644 --- a/content/docs/installation/upgrade.md +++ b/content/docs/installation/upgrade.md @@ -3,9 +3,10 @@ title: Upgrading cert-manager description: 'cert-manager installation: Upgrading cert-manager overview' --- -This section contains information on upgrading cert-manager. -It also contains documents detailing breaking changes between cert-manager -versions, and information on things to look out for when upgrading. +In the [releases section](../releases/README.md) of the documentation, you can find the release notes +and upgrade instructions for each release of cert-manager. It also contains +information on the breaking changes between each release and things to look out +for when upgrading. > Note: Before performing upgrades of cert-manager, it is advised to take a > backup of all your cert-manager resources just in case an issue occurs whilst From 5afffded856ebd7effb81fe3fb707e05d2d01afa Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 25 Oct 2023 08:31:23 +0100 Subject: [PATCH 221/264] Add link to RedHat OpenShift pod placement documentation Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 2d9d639353..d79c513b48 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -112,6 +112,8 @@ startupapicheck: > > 📖 Learn how to [Isolate your workloads in dedicated node pools](https://cloud.google.com/kubernetes-engine/docs/how-to/isolate-workloads-dedicated-nodes) on [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/docs/). > +> 📖 Learn about [Placing pods on specific nodes using node selectors, with RedHat OpenShift](https://docs.openshift.com/container-platform/4.13/nodes/scheduling/nodes-scheduler-node-selectors.html). +> > 📖 Read more about the [`node-restriction.kubernetes.io/` prefix and the `NodeRestriction` admission plugin](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction). > > ℹ️ On a multi-tenant cluster, From 7b22cba1d9d3f94aaf0f3af0befdf1c24e563e74 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 25 Oct 2023 09:08:29 +0100 Subject: [PATCH 222/264] Move links to pod placement docs nearer to where concepts are introduced Thanks @schelv for the suggestion. Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index d79c513b48..75558131ac 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -38,6 +38,12 @@ You can mitigate this risk by running cert-manager on nodes that are reserved fo The Helm chart for cert-manager has parameters to configure the Pod `tolerations` and `nodeSelector` for each component. The exact values of these parameters will depend on your particular cluster. +> 📖 Read [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) +> in the [Kubernetes documentation](https://kubernetes.io/docs/). +> +> 📖 Read about [Taints and Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) +> in the [Kubernetes documentation](https://kubernetes.io/docs/). + ### Example This example demonstrates how to use: @@ -98,14 +104,10 @@ startupapicheck: > ℹ️ This example uses `nodeSelector` to *place* the Pods but you could also use `affinity.nodeAffinity`. > `nodeSelector` is chosen here because it has a simpler syntax. -> Read [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) to learn more. > > ℹ️ The default `nodeSelector` value `kubernetes.io/os: linux` [avoids placing cert-manager Pods on Windows nodes in a mixed OS cluster](https://github.com/cert-manager/cert-manager/pull/3605), > so that must be explicitly included here too. > -> 📖 Read more about [Taints and Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) -> in the [Kubernetes documentation](https://kubernetes.io/docs/). -> > 📖 Read the [Guide to isolating tenant workloads to specific nodes](https://aws.github.io/aws-eks-best-practices/security/docs/multitenancy/#isolating-tenant-workloads-to-specific-nodes) > in the [EKS Best Practice Guides](https://aws.github.io/aws-eks-best-practices/), > for an in-depth explanation of these techniques. From beca28894e68c4f63d812a57cc1b1b4f08f9a219 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 25 Oct 2023 15:47:35 +0200 Subject: [PATCH 223/264] link to details about reissuance & flatten title structure Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/usage/certificate.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index fae7d74ceb..b3998619f6 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -249,6 +249,7 @@ A certificate object is reissued under the following circumstances: - when a change is made to one of the following fields on the Certificate's spec: `commonName`, `dnsNames`, `ipAddresses`, `uris`, `emailAddresses`, `subject`, `isCA`, `usages`, `duration` or `issuerRef`; + A more detailed explanation can be found on the [FAQ page](../faq/README.md#when-do-certs-get-re-issued). - when a reissuance is manually triggered with the following: ```sh cmctl renew cert-1 @@ -269,10 +270,8 @@ cmctl renew cert-1
              -## Issuance behavior - -### Temporary Certificates while Issuing +## Issuance behavior: Temporary Certificates while Issuing When requesting certificates [using the ingress-shim](./ingress.md), the component `ingress-gce`, if used, requires that a temporary certificate is @@ -294,7 +293,7 @@ Adding the following annotation on an ingress will automatically set "issue-temp ``` -### Rotation of the private key +## Issuance behavior: Rotation of the private key By default, the private key won't be rotated automatically. Using the setting `rotationPolicy: Always`, the private key Secret associated with a Certificate From c7a25e8c8958dfbe7b6bf38a4aeb8231c6a73188 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Wed, 25 Oct 2023 15:19:26 +0100 Subject: [PATCH 224/264] fix title casing in sidebar Signed-off-by: Ashley Davis --- content/docs/manifest.json | 56 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 8d3ec903f1..6d81d5889c 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -24,7 +24,7 @@ "path": "/docs/releases/release-notes/release-notes-1.13.md" }, { - "title": "upgrade 1.12 to 1.13", + "title": "Upgrade 1.12 to 1.13", "path": "/docs/releases/upgrading/upgrading-1.12-1.13.md" }, { @@ -32,7 +32,7 @@ "path": "/docs/releases/release-notes/release-notes-1.12.md" }, { - "title": "upgrade 1.11 to 1.12", + "title": "Upgrade 1.11 to 1.12", "path": "/docs/releases/upgrading/upgrading-1.11-1.12.md" }, { @@ -51,7 +51,7 @@ "path": "/docs/releases/release-notes/release-notes-1.11.md" }, { - "title": "upgrade 1.10 to 1.11", + "title": "Upgrade 1.10 to 1.11", "path": "/docs/releases/upgrading/upgrading-1.10-1.11.md" }, { @@ -59,7 +59,7 @@ "path": "/docs/releases/release-notes/release-notes-1.10.md" }, { - "title": "upgrade 1.9 to 1.10", + "title": "Upgrade 1.9 to 1.10", "path": "/docs/releases/upgrading/upgrading-1.9-1.10.md" }, { @@ -67,7 +67,7 @@ "path": "/docs/releases/release-notes/release-notes-1.9.md" }, { - "title": "upgrade 1.8 to 1.9", + "title": "Upgrade 1.8 to 1.9", "path": "/docs/releases/upgrading/upgrading-1.8-1.9.md" }, { @@ -75,7 +75,7 @@ "path": "/docs/releases/release-notes/release-notes-1.8.md" }, { - "title": "upgrade 1.7 to 1.8", + "title": "Upgrade 1.7 to 1.8", "path": "/docs/releases/upgrading/upgrading-1.7-1.8.md" }, { @@ -83,7 +83,7 @@ "path": "/docs/releases/release-notes/release-notes-1.7.md" }, { - "title": "upgrade 1.6 to 1.7", + "title": "Upgrade 1.6 to 1.7", "path": "/docs/releases/upgrading/upgrading-1.6-1.7.md" }, { @@ -91,7 +91,7 @@ "path": "/docs/releases/release-notes/release-notes-1.6.md" }, { - "title": "upgrade 1.5 to 1.6", + "title": "Upgrade 1.5 to 1.6", "path": "/docs/releases/upgrading/upgrading-1.5-1.6.md" }, { @@ -99,7 +99,7 @@ "path": "/docs/releases/release-notes/release-notes-1.5.md" }, { - "title": "upgrade 1.4 to 1.5", + "title": "Upgrade 1.4 to 1.5", "path": "/docs/releases/upgrading/upgrading-1.4-1.5.md" }, { @@ -107,7 +107,7 @@ "path": "/docs/releases/release-notes/release-notes-1.4.md" }, { - "title": "upgrade 1.3 to 1.4", + "title": "Upgrade 1.3 to 1.4", "path": "/docs/releases/upgrading/upgrading-1.3-1.4.md" }, { @@ -115,7 +115,7 @@ "path": "/docs/releases/release-notes/release-notes-1.3.md" }, { - "title": "upgrade 1.2 to 1.3", + "title": "Upgrade 1.2 to 1.3", "path": "/docs/releases/upgrading/upgrading-1.2-1.3.md" }, { @@ -123,7 +123,7 @@ "path": "/docs/releases/release-notes/release-notes-1.2.md" }, { - "title": "upgrade 1.1 to 1.2", + "title": "Upgrade 1.1 to 1.2", "path": "/docs/releases/upgrading/upgrading-1.1-1.2.md" }, { @@ -131,7 +131,7 @@ "path": "/docs/releases/release-notes/release-notes-1.1.md" }, { - "title": "upgrade 1.0 to 1.1", + "title": "Upgrade 1.0 to 1.1", "path": "/docs/releases/upgrading/upgrading-1.0-1.1.md" }, { @@ -139,7 +139,7 @@ "path": "/docs/releases/release-notes/release-notes-1.0.md" }, { - "title": "upgrade 0.16 to 1.0", + "title": "Upgrade 0.16 to 1.0", "path": "/docs/releases/upgrading/upgrading-0.16-1.0.md" }, { @@ -147,7 +147,7 @@ "path": "/docs/releases/release-notes/release-notes-0.16.md" }, { - "title": "upgrade 0.15 to 0.16", + "title": "Upgrade 0.15 to 0.16", "path": "/docs/releases/upgrading/upgrading-0.15-0.16.md" }, { @@ -155,7 +155,7 @@ "path": "/docs/releases/release-notes/release-notes-0.15.md" }, { - "title": "upgrade 0.14 to 0.15", + "title": "Upgrade 0.14 to 0.15", "path": "/docs/releases/upgrading/upgrading-0.14-0.15.md" }, { @@ -163,7 +163,7 @@ "path": "/docs/releases/release-notes/release-notes-0.14.md" }, { - "title": "upgrade 0.13 to 0.14", + "title": "Upgrade 0.13 to 0.14", "path": "/docs/releases/upgrading/upgrading-0.13-0.14.md" }, { @@ -171,7 +171,7 @@ "path": "/docs/releases/release-notes/release-notes-0.13.md" }, { - "title": "upgrade 0.12 to 0.13", + "title": "Upgrade 0.12 to 0.13", "path": "/docs/releases/upgrading/upgrading-0.12-0.13.md" }, { @@ -179,7 +179,7 @@ "path": "/docs/releases/release-notes/release-notes-0.12.md" }, { - "title": "upgrade 0.11 to 0.12", + "title": "Upgrade 0.11 to 0.12", "path": "/docs/releases/upgrading/upgrading-0.11-0.12.md" }, { @@ -187,7 +187,7 @@ "path": "/docs/releases/release-notes/release-notes-0.11.md" }, { - "title": "upgrade 0.10 to 0.11", + "title": "Upgrade 0.10 to 0.11", "path": "/docs/releases/upgrading/upgrading-0.10-0.11.md" }, { @@ -195,7 +195,7 @@ "path": "/docs/releases/release-notes/release-notes-0.10.md" }, { - "title": "upgrade 0.9 to 0.10", + "title": "Upgrade 0.9 to 0.10", "path": "/docs/releases/upgrading/upgrading-0.9-0.10.md" }, { @@ -203,7 +203,7 @@ "path": "/docs/releases/release-notes/release-notes-0.9.md" }, { - "title": "upgrade 0.8 to 0.9", + "title": "Upgrade 0.8 to 0.9", "path": "/docs/releases/upgrading/upgrading-0.8-0.9.md" }, { @@ -211,7 +211,7 @@ "path": "/docs/releases/release-notes/release-notes-0.8.md" }, { - "title": "upgrade 0.7 to 0.8", + "title": "Upgrade 0.7 to 0.8", "path": "/docs/releases/upgrading/upgrading-0.7-0.8.md" }, { @@ -219,7 +219,7 @@ "path": "/docs/releases/release-notes/release-notes-0.7.md" }, { - "title": "upgrade 0.6 to 0.7", + "title": "Upgrade 0.6 to 0.7", "path": "/docs/releases/upgrading/upgrading-0.6-0.7.md" }, { @@ -227,7 +227,7 @@ "path": "/docs/releases/release-notes/release-notes-0.6.md" }, { - "title": "upgrade 0.5 to 0.6", + "title": "Upgrade 0.5 to 0.6", "path": "/docs/releases/upgrading/upgrading-0.5-0.6.md" }, { @@ -235,7 +235,7 @@ "path": "/docs/releases/release-notes/release-notes-0.5.md" }, { - "title": "upgrade 0.4 to 0.5", + "title": "Upgrade 0.4 to 0.5", "path": "/docs/releases/upgrading/upgrading-0.4-0.5.md" }, { @@ -243,7 +243,7 @@ "path": "/docs/releases/release-notes/release-notes-0.4.md" }, { - "title": "upgrade 0.3 to 0.4", + "title": "Upgrade 0.3 to 0.4", "path": "/docs/releases/upgrading/upgrading-0.3-0.4.md" }, { @@ -251,7 +251,7 @@ "path": "/docs/releases/release-notes/release-notes-0.3.md" }, { - "title": "upgrade 0.2 to 0.3", + "title": "Upgrade 0.2 to 0.3", "path": "/docs/releases/upgrading/upgrading-0.2-0.3.md" }, { From 60814d8879edabd5413a5b043996e7cc2c29371e Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:34:22 +0100 Subject: [PATCH 225/264] add release notes for v1.13.2 Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 1 + .../release-notes/release-notes-1.13.md | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/.spelling b/.spelling index e4fcf161b4..d000b4f26e 100644 --- a/.spelling +++ b/.spelling @@ -623,6 +623,7 @@ v4.4.1 v1.13.0 v1.13.0. v1.13.1 +v1.13.2 v1.13. v1.12.5 liveness diff --git a/content/docs/releases/release-notes/release-notes-1.13.md b/content/docs/releases/release-notes/release-notes-1.13.md index a8595b18fd..dd72d96aad 100644 --- a/content/docs/releases/release-notes/release-notes-1.13.md +++ b/content/docs/releases/release-notes/release-notes-1.13.md @@ -3,6 +3,26 @@ title: Release 1.13 description: 'cert-manager release notes: cert-manager 1.13' --- +## v1.13.2 + +v1.13.2 fixes some CVE alerts and contains fixes for: +1. a CertificateRequest runaway situation in case two Certificate resources point to the same Secret target resource +2. a small bug in the Helm chart (feature gate options) +3. a Venafi issuer bug + +### Changes + +#### Bug or Regression + +- Bump `golang.org/x/net v0.15.0 => v0.17.0` as part of addressing `CVE-2023-44487` / `CVE-2023-39325` (#6432, @SgtCoDFish) +- BUGFIX[helm]: Fix issue where webhook feature gates were only set if controller feature gates are set. (#6381, @jetstack-bot) +- Fix runaway bug caused by multiple Certificate resources that point to the same Secret resource. (#6425, @jetstack-bot) +- The Venafi issuer now properly resets the certificate and should no longer get stuck with `WebSDK CertRequest Module Requested Certificate` or `This certificate cannot be processed while it is in an error state. Fix any errors, and then click Retry.`. (#6402, @jetstack-bot) + +#### Other (Cleanup or Flake) + +- Bump go to 1.20.10 to address `CVE-2023-39325`. Also bumps base images. (#6411, @SgtCoDFish) + ## v1.13.1 v1.13.1 contains a bugfix for a name collision bug in the StableCertificateRequestName feature that was enabled by default in v1.13.0. From 2487b2f753cbeda189e77cfe699d1ceb71d49a62 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:35:39 +0100 Subject: [PATCH 226/264] add release notes for v1.12.6 Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 1 + .../releases/release-notes/release-notes-1.12.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/.spelling b/.spelling index e4fcf161b4..6f29d45f7c 100644 --- a/.spelling +++ b/.spelling @@ -625,6 +625,7 @@ v1.13.0. v1.13.1 v1.13. v1.12.5 +v1.12.6 liveness apiservices arm64 diff --git a/content/docs/releases/release-notes/release-notes-1.12.md b/content/docs/releases/release-notes/release-notes-1.12.md index 97dddca56c..ebb3b26a57 100644 --- a/content/docs/releases/release-notes/release-notes-1.12.md +++ b/content/docs/releases/release-notes/release-notes-1.12.md @@ -3,6 +3,21 @@ title: Release 1.12 description: 'cert-manager release notes: cert-manager 1.12' --- +## v1.12.6 + +v1.12.6 fixes some CVE alerts and a Venafi issuer bug + +### Changes + +#### Bug or Regression + +- Bump `golang.org/x/net v0.15.0 => v0.17.0` as part of addressing `CVE-2023-44487` / `CVE-2023-39325` (#6431, @SgtCoDFish) +- The Venafi issuer now properly resets the certificate and should no longer get stuck with `WebSDK CertRequest Module Requested Certificate` or `This certificate cannot be processed while it is in an error state. Fix any errors, and then click Retry.`. (#6401, @jetstack-bot) + +#### Other (Cleanup or Flake) + +- Bump go to 1.20.10 to address `CVE-2023-39325`. Also bumps base images. (#6412, @SgtCoDFish) + ## v1.12.5 v1.12.5 contains a backport for a name collision bug that was found in v1.13.0 From 2ff4bea66e74874349e12c083ba22ed3803fdc06 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:18:02 +0100 Subject: [PATCH 227/264] update documentation to update to latest patch version Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/cli/controller.md | 2 +- content/docs/installation/README.md | 2 +- content/docs/installation/code-signing.md | 2 +- content/docs/installation/helm.md | 10 +++++----- content/docs/installation/kubectl.md | 2 +- .../docs/installation/operator-lifecycle-manager.md | 2 +- content/v1.13-docs/installation/README.md | 2 +- content/v1.13-docs/installation/code-signing.md | 2 +- content/v1.13-docs/installation/helm.md | 10 +++++----- content/v1.13-docs/installation/kubectl.md | 2 +- .../installation/operator-lifecycle-manager.md | 2 +- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/content/docs/cli/controller.md b/content/docs/cli/controller.md index 4121d6b093..7da64fa882 100644 --- a/content/docs/cli/controller.md +++ b/content/docs/cli/controller.md @@ -14,7 +14,7 @@ Usage: controller [flags] Flags: - --acme-http01-solver-image string The docker image to use to solve ACME HTTP01 challenges. You most likely will not need to change this parameter unless you are testing a new feature or developing cert-manager. (default "quay.io/jetstack/cert-manager-acmesolver:v1.13.1") + --acme-http01-solver-image string The docker image to use to solve ACME HTTP01 challenges. You most likely will not need to change this parameter unless you are testing a new feature or developing cert-manager. (default "quay.io/jetstack/cert-manager-acmesolver:v1.13.2") --acme-http01-solver-nameservers strings A list of comma separated dns server endpoints used for ACME HTTP01 check requests. This should be a list containing host and port, for example 8.8.8.8:53,8.8.4.4:53 --acme-http01-solver-resource-limits-cpu string Defines the resource limits CPU size when spawning new ACME HTTP01 challenge solver pods. (default "100m") --acme-http01-solver-resource-limits-memory string Defines the resource limits Memory size when spawning new ACME HTTP01 challenge solver pods. (default "64Mi") diff --git a/content/docs/installation/README.md b/content/docs/installation/README.md index 1dd0eaec36..d15788c88c 100644 --- a/content/docs/installation/README.md +++ b/content/docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/docs/installation/code-signing.md b/content/docs/installation/code-signing.md index 52aa725c6b..c6cade8771 100644 --- a/content/docs/installation/code-signing.md +++ b/content/docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.13.1 # change as needed +IMAGE_TAG=v1.13.2 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index 02a324bacf..b5a78cb483 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -47,7 +47,7 @@ section below for details on each method. > Recommended for production installations ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -70,7 +70,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.1 \ + --version v1.13.2 \ # --set installCRDs=true ``` @@ -83,7 +83,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.1 \ + --version v1.13.2 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -114,7 +114,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.13.1 + version: v1.13.2 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -148,7 +148,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.1 \ + --version v1.13.2 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/docs/installation/kubectl.md b/content/docs/installation/kubectl.md index 405ece3db8..c6a787a86b 100644 --- a/content/docs/installation/kubectl.md +++ b/content/docs/installation/kubectl.md @@ -21,7 +21,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index 2b7b57d1b2..5bc2f506cd 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.13.1 \ +kubectl patch csv cert-manager.v1.13.2 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` diff --git a/content/v1.13-docs/installation/README.md b/content/v1.13-docs/installation/README.md index 1dd0eaec36..d15788c88c 100644 --- a/content/v1.13-docs/installation/README.md +++ b/content/v1.13-docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/v1.13-docs/installation/code-signing.md b/content/v1.13-docs/installation/code-signing.md index 52aa725c6b..c6cade8771 100644 --- a/content/v1.13-docs/installation/code-signing.md +++ b/content/v1.13-docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.13.1 # change as needed +IMAGE_TAG=v1.13.2 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/v1.13-docs/installation/helm.md b/content/v1.13-docs/installation/helm.md index c979d63424..e284c16178 100644 --- a/content/v1.13-docs/installation/helm.md +++ b/content/v1.13-docs/installation/helm.md @@ -47,7 +47,7 @@ section below for details on each method. > Recommended for production installations ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -70,7 +70,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.1 \ + --version v1.13.2 \ # --set installCRDs=true ``` @@ -83,7 +83,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.1 \ + --version v1.13.2 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -114,7 +114,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.13.1 + version: v1.13.2 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -148,7 +148,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.13.1 \ + --version v1.13.2 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/v1.13-docs/installation/kubectl.md b/content/v1.13-docs/installation/kubectl.md index 117ccb884b..c0beb42129 100644 --- a/content/v1.13-docs/installation/kubectl.md +++ b/content/v1.13-docs/installation/kubectl.md @@ -19,7 +19,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/v1.13-docs/installation/operator-lifecycle-manager.md b/content/v1.13-docs/installation/operator-lifecycle-manager.md index b525c41edb..1ed323caec 100644 --- a/content/v1.13-docs/installation/operator-lifecycle-manager.md +++ b/content/v1.13-docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.13.1 \ +kubectl patch csv cert-manager.v1.13.2 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` From ef867caf66f01c8dc16acb6b3f49587c57c1a41a Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:43:36 +0100 Subject: [PATCH 228/264] bump v1.12.5 to v1.12.6 Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/v1.12-docs/installation/README.md | 2 +- content/v1.12-docs/installation/code-signing.md | 2 +- content/v1.12-docs/installation/helm.md | 10 +++++----- content/v1.12-docs/installation/kubectl.md | 2 +- .../installation/operator-lifecycle-manager.md | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/content/v1.12-docs/installation/README.md b/content/v1.12-docs/installation/README.md index 741d8e3a98..4bcccf6ee9 100644 --- a/content/v1.12-docs/installation/README.md +++ b/content/v1.12-docs/installation/README.md @@ -12,7 +12,7 @@ Learn about the various ways you can install cert-manager and how to choose betw The default static configuration can be installed as follows: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.5/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.6/cert-manager.yaml ``` 📖 Read more about [installing cert-manager using kubectl apply and static manifests](./kubectl.md). diff --git a/content/v1.12-docs/installation/code-signing.md b/content/v1.12-docs/installation/code-signing.md index 534a8c97fd..f85017b877 100644 --- a/content/v1.12-docs/installation/code-signing.md +++ b/content/v1.12-docs/installation/code-signing.md @@ -22,7 +22,7 @@ The simplest way to verify signatures is to download the public key and then pas ```console curl -sSOL https://cert-manager.io/public-keys/cert-manager-pubkey-2021-09-20.pem -IMAGE_TAG=v1.12.5 # change as needed +IMAGE_TAG=v1.12.6 # change as needed cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-acmesolver:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-cainjector:$IMAGE_TAG cosign verify --signature-digest-algorithm sha512 --key cert-manager-pubkey-2021-09-20.pem quay.io/jetstack/cert-manager-ctl:$IMAGE_TAG diff --git a/content/v1.12-docs/installation/helm.md b/content/v1.12-docs/installation/helm.md index 4086c63cad..64845005c1 100644 --- a/content/v1.12-docs/installation/helm.md +++ b/content/v1.12-docs/installation/helm.md @@ -44,7 +44,7 @@ or using the `installCRDs` option when installing the Helm chart. ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.5/cert-manager.crds.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.6/cert-manager.crds.yaml ``` ##### Option 2: install CRDs as part of the Helm release @@ -65,7 +65,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.5 \ + --version v1.12.6 \ # --set installCRDs=true ``` @@ -78,7 +78,7 @@ helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.5 \ + --version v1.12.6 \ # --set installCRDs=true --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter --set webhook.timeoutSeconds=4 # Example: changing the webhook timeout using a Helm parameter @@ -109,7 +109,7 @@ version: 0.1.0 appVersion: "0.1.0" dependencies: - name: cert-manager - version: v1.12.5 + version: v1.12.6 repository: https://charts.jetstack.io alias: cert-manager condition: cert-manager.enabled @@ -140,7 +140,7 @@ helm template \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ - --version v1.12.5 \ + --version v1.12.6 \ # --set prometheus.enabled=false \ # Example: disabling prometheus using a Helm parameter # --set installCRDs=true \ # Uncomment to also template CRDs > cert-manager.custom.yaml diff --git a/content/v1.12-docs/installation/kubectl.md b/content/v1.12-docs/installation/kubectl.md index 05a7237a67..f93d6ece06 100644 --- a/content/v1.12-docs/installation/kubectl.md +++ b/content/v1.12-docs/installation/kubectl.md @@ -19,7 +19,7 @@ are included in a single YAML manifest file: Install all cert-manager components: ```bash -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.5/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.6/cert-manager.yaml ``` By default, cert-manager will be installed into the `cert-manager` diff --git a/content/v1.12-docs/installation/operator-lifecycle-manager.md b/content/v1.12-docs/installation/operator-lifecycle-manager.md index df6a7b6ece..c24591a385 100644 --- a/content/v1.12-docs/installation/operator-lifecycle-manager.md +++ b/content/v1.12-docs/installation/operator-lifecycle-manager.md @@ -217,7 +217,7 @@ The following JSON patch will append `-v=6` to command line arguments of the cer (the first container of the first Deployment). ```bash -kubectl patch csv cert-manager.v1.12.5 \ +kubectl patch csv cert-manager.v1.12.6 \ --type json \ -p '[{"op": "add", "path": "/spec/install/spec/deployments/0/spec/template/spec/containers/0/args/-", "value": "-v=6" }]' ``` From affbf01f9c131014e86b46a0b329f43893ba1779 Mon Sep 17 00:00:00 2001 From: Elan Hasson <234704+ElanHasson@users.noreply.github.com> Date: Tue, 31 Oct 2023 23:33:31 -0400 Subject: [PATCH 229/264] PR Feedback Signed-off-by: Elan Hasson <234704+ElanHasson@users.noreply.github.com> --- content/docs/installation/operator-lifecycle-manager.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index 15951779cc..d21bb2f399 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -41,7 +41,8 @@ from the [Krew Kubectl plugins index][] and then use that to install the cert-ma ```sh operator-sdk olm install kubectl krew install operator -kubectl operator install cert-manager -n cert-manager --channel candidate --approval Automatic --create-operator-group +kubectl create ns cert-manager +kubectl operator install cert-manager -n operators --channel stable --approval Automatic --create-operator-group -n cert-manager ``` You can monitor the progress of the installation as follows: From 5b0ff91617901bfe81f102496cf673eb04711692 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 1 Nov 2023 09:47:26 +0000 Subject: [PATCH 230/264] Update content/docs/installation/operator-lifecycle-manager.md Signed-off-by: Richard Wall --- content/docs/installation/operator-lifecycle-manager.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/operator-lifecycle-manager.md b/content/docs/installation/operator-lifecycle-manager.md index d21bb2f399..180fe8da45 100644 --- a/content/docs/installation/operator-lifecycle-manager.md +++ b/content/docs/installation/operator-lifecycle-manager.md @@ -42,7 +42,7 @@ from the [Krew Kubectl plugins index][] and then use that to install the cert-ma operator-sdk olm install kubectl krew install operator kubectl create ns cert-manager -kubectl operator install cert-manager -n operators --channel stable --approval Automatic --create-operator-group -n cert-manager +kubectl operator install cert-manager -n cert-manager --channel stable --approval Automatic --create-operator-group ``` You can monitor the progress of the installation as follows: From a8abf6975478ddfa117cd25591923f02408ae0ea Mon Sep 17 00:00:00 2001 From: tspearconquest <81998567+tspearconquest@users.noreply.github.com> Date: Wed, 1 Nov 2023 15:06:53 -0500 Subject: [PATCH 231/264] Update README.md Signed-off-by: tspearconquest <81998567+tspearconquest@users.noreply.github.com> --- content/docs/trust/trust-manager/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/trust/trust-manager/README.md b/content/docs/trust/trust-manager/README.md index b3a88c3547..9173c5160e 100644 --- a/content/docs/trust/trust-manager/README.md +++ b/content/docs/trust/trust-manager/README.md @@ -157,8 +157,8 @@ we currently have available. One of the more important configuration options you might need to consider at install time is which "trust namespace" to use, which can be set via the Helm value `app.trust.namespace`. -The trust namespace is the only one in which `Secret` and `ConfigMap` sources can be read. This restriction is in place -for security reasons - we don't want to give trust-manager the permission to read all `Secret`s or `ConfigMap`s in all namespaces. +The trust namespace is the only one in which `Secret` sources can be read. This restriction is in place +for security reasons - we don't want to give trust-manager the permission to read all `Secret`s in all namespaces. The trust namespace defaults to `cert-manager`, but there's no need for it to be set to the namespace that cert-manager is installed in - trust-manager has no runtime dependency on cert-manager at all! - so we'd recommend setting the trust From e413c0cced48eb165db9d46f451fab5347e8cecf Mon Sep 17 00:00:00 2001 From: tspearconquest <81998567+tspearconquest@users.noreply.github.com> Date: Wed, 1 Nov 2023 15:13:03 -0500 Subject: [PATCH 232/264] Update content/docs/trust/trust-manager/README.md Co-authored-by: Josh van Leeuwen Signed-off-by: tspearconquest <81998567+tspearconquest@users.noreply.github.com> --- content/docs/trust/trust-manager/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/trust/trust-manager/README.md b/content/docs/trust/trust-manager/README.md index 9173c5160e..83c828bb25 100644 --- a/content/docs/trust/trust-manager/README.md +++ b/content/docs/trust/trust-manager/README.md @@ -157,7 +157,7 @@ we currently have available. One of the more important configuration options you might need to consider at install time is which "trust namespace" to use, which can be set via the Helm value `app.trust.namespace`. -The trust namespace is the only one in which `Secret` sources can be read. This restriction is in place +By default, the trust namespace is the only one in which `Secret`s can be read. This restriction is in place for security reasons - we don't want to give trust-manager the permission to read all `Secret`s in all namespaces. The trust namespace defaults to `cert-manager`, but there's no need for it to be set to the namespace that cert-manager From 6ee7a531741862990ca5e46bdb0826b4a554f1a4 Mon Sep 17 00:00:00 2001 From: tspearconquest <81998567+tspearconquest@users.noreply.github.com> Date: Wed, 1 Nov 2023 16:19:33 -0500 Subject: [PATCH 233/264] Update content/docs/trust/trust-manager/README.md Signed-off-by: tspearconquest <81998567+tspearconquest@users.noreply.github.com> --- content/docs/trust/trust-manager/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/trust/trust-manager/README.md b/content/docs/trust/trust-manager/README.md index 83c828bb25..ee23140a3e 100644 --- a/content/docs/trust/trust-manager/README.md +++ b/content/docs/trust/trust-manager/README.md @@ -158,7 +158,7 @@ One of the more important configuration options you might need to consider at in which can be set via the Helm value `app.trust.namespace`. By default, the trust namespace is the only one in which `Secret`s can be read. This restriction is in place -for security reasons - we don't want to give trust-manager the permission to read all `Secret`s in all namespaces. +for security reasons - we don't want to give trust-manager the permission to read all `Secret`s in all namespaces. With additional configuration, secrets may be read from or written to other namespaces. The trust namespace defaults to `cert-manager`, but there's no need for it to be set to the namespace that cert-manager is installed in - trust-manager has no runtime dependency on cert-manager at all! - so we'd recommend setting the trust From 3f767fd271048b3b909917f9cde553cd81069ddf Mon Sep 17 00:00:00 2001 From: tspearconquest <81998567+tspearconquest@users.noreply.github.com> Date: Wed, 1 Nov 2023 16:19:38 -0500 Subject: [PATCH 234/264] Update content/docs/trust/trust-manager/README.md Signed-off-by: tspearconquest <81998567+tspearconquest@users.noreply.github.com> --- content/docs/trust/trust-manager/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/trust/trust-manager/README.md b/content/docs/trust/trust-manager/README.md index ee23140a3e..94a94a416d 100644 --- a/content/docs/trust/trust-manager/README.md +++ b/content/docs/trust/trust-manager/README.md @@ -157,7 +157,7 @@ we currently have available. One of the more important configuration options you might need to consider at install time is which "trust namespace" to use, which can be set via the Helm value `app.trust.namespace`. -By default, the trust namespace is the only one in which `Secret`s can be read. This restriction is in place +By default, the trust namespace is the only namespace where`Secret`s will be read. This restriction is in place for security reasons - we don't want to give trust-manager the permission to read all `Secret`s in all namespaces. With additional configuration, secrets may be read from or written to other namespaces. The trust namespace defaults to `cert-manager`, but there's no need for it to be set to the namespace that cert-manager From 0cb83769be3f1add12e6239e7097f68e4e141521 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Thu, 2 Nov 2023 15:24:08 +0000 Subject: [PATCH 235/264] Explain how to install cert-manager using Flux Signed-off-by: Richard Wall --- content/docs/installation/helm.md | 97 ++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index b5a78cb483..0aabc50087 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -247,7 +247,7 @@ of their approach [here](https://helm.sh/docs/chart_best_practices/custom_resour cert-manager actually bundles the CRDs along with the other templates in the Helm chart. This means that Helm manages these resources so they are -upgraded with your cert-manager release when you use +upgraded with your cert-manager release when you use `installCRDs: true` in your values file or CLI command. This does also mean that if you uninstall the release, the CRDs will also be uninstalled. If that happens then you will loose all instances of those CRDs, e.g. all `Certificate` @@ -282,7 +282,100 @@ Generally we recommend: You may want to consider your approach along with other tools that may offer helm compatible installs, for a standardized approach to managing CRD resources. If you have an approach that cert-manager does not currently -support, then please +support, then please [raise an issue](https://github.com/cert-manager/cert-manager/issues) to discuss. + +## Install the Helm chart using Flux + +The cert-manager Helm chart can be installed by [Flux](https://fluxcd.io/). + +First create a [`HelmRepository` resource](https://fluxcd.io/flux/components/source/helmrepositories/), +configured with URL of the cert-manager Helm repository. +Then create a [`HelmRelease` resource](https://fluxcd.io/flux/components/helm/helmreleases/), +configured with your desired cert-manager chart values and release. + +Here is an example which installs the latest patch version of the cert-manager 1.12 release, +and then upgrades to the latest patch version of the 1.13 release. + +### Prerequisites + +You'll need the [`flux` CLI](https://fluxcd.io/flux/cmd/) +and a Kubernetes cluster with [Flux installed](https://fluxcd.io/flux/installation/). + +Here's how to quickly install Flux on a [Kind](https://kind.sigs.k8s.io/) cluster: + +```bash +kind create cluster +flux check --pre +flux install +flux check +``` + +### Create a `HelmRepository` resource + +```bash +flux create source helm cert-manager --url https://charts.jetstack.io +``` + +### Create a `HelmRelease` resource + +Put your Helm chart values in a `values.yaml` file. +Use the `installCRDs` value, so that Flux can install and upgrade the CRD resources. + +```yaml +# values.yaml +installCRDs: true +``` + +```bash +flux create helmrelease cert-manager \ + --chart cert-manager \ + --source HelmRepository/cert-manager.flux-system \ + --release-name cert-manager \ + --target-namespace cert-manager \ + --create-target-namespace \ + --crds CreateReplace \ + --values values.yaml \ + --chart-version '>1.12.0 <1.13.0' +``` + +### Updates and Upgrades + +And when you want to upgrade to the cert-manager 1.13 release, +you can simply update the semantic version range in the chart version: + +```bash +flux create helmrelease cert-manager \ + --chart cert-manager \ + --source HelmRepository/cert-manager.flux-system \ + --release-name cert-manager \ + --target-namespace cert-manager \ + --create-target-namespace \ + --crds CreateReplace \ + --values values.yaml \ + --chart-version '>1.12.0 <1.14.0' +``` + +### Troubleshooting + +Check Flux events and logs for warnings and errors: + +```bash +flux events +flux logs +``` + +Use `cmctl` to check for problems with the cert-manager webhook or CRDs: + +```bash +cmctl check api +cmctl version -o yaml +``` + +Check the cert-manager logs for warnings and errors: + +```bash +kubectl logs -n cert-manager -l app.kubernetes.io/instance=cert-manager --prefix --all-containers +``` From 3bda8cb9c142a322c45b6ffc74d5bd8f8afed88b Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Thu, 2 Nov 2023 15:45:05 +0000 Subject: [PATCH 236/264] Update content/docs/installation/helm.md Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> Signed-off-by: Richard Wall --- content/docs/installation/helm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index 0aabc50087..6bbb421fba 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -292,7 +292,7 @@ discuss. The cert-manager Helm chart can be installed by [Flux](https://fluxcd.io/). First create a [`HelmRepository` resource](https://fluxcd.io/flux/components/source/helmrepositories/), -configured with URL of the cert-manager Helm repository. +configured with URL of the cert-manager Helm repository. Then create a [`HelmRelease` resource](https://fluxcd.io/flux/components/helm/helmreleases/), configured with your desired cert-manager chart values and release. From 72b590e1418292ff08f6a47ffc692f4b34f74902 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 3 Nov 2023 11:14:40 +0000 Subject: [PATCH 237/264] Use the shorter .x semver range syntax Signed-off-by: Richard Wall --- content/docs/installation/helm.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index 6bbb421fba..1e911a10bd 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -338,13 +338,13 @@ flux create helmrelease cert-manager \ --create-target-namespace \ --crds CreateReplace \ --values values.yaml \ - --chart-version '>1.12.0 <1.13.0' + --chart-version 1.12.x ``` ### Updates and Upgrades And when you want to upgrade to the cert-manager 1.13 release, -you can simply update the semantic version range in the chart version: +you can simply update the partial semantic version in the chart version: ```bash flux create helmrelease cert-manager \ @@ -355,7 +355,7 @@ flux create helmrelease cert-manager \ --create-target-namespace \ --crds CreateReplace \ --values values.yaml \ - --chart-version '>1.12.0 <1.14.0' + --chart-version 1.13.x ``` ### Troubleshooting From c88984a7f1513b6478b1178acf696bff26e1a88a Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Fri, 3 Nov 2023 12:29:20 +0000 Subject: [PATCH 238/264] Create a new Continuous Deployment page Signed-off-by: Richard Wall --- content/docs/installation/README.md | 11 +- .../continuous-deployment-and-gitops.md | 110 ++++++++++++++++++ content/docs/installation/helm.md | 94 +-------------- content/docs/manifest.json | 4 + 4 files changed, 122 insertions(+), 97 deletions(-) create mode 100644 content/docs/installation/continuous-deployment-and-gitops.md diff --git a/content/docs/installation/README.md b/content/docs/installation/README.md index d15788c88c..2237153d06 100644 --- a/content/docs/installation/README.md +++ b/content/docs/installation/README.md @@ -32,10 +32,9 @@ which you can do from the OpenShift web console. ## Continuous deployment -> You know how to configure your cert-manager setup and want to automate this. +> If you know how to configure your cert-manager setup and want to automate this, +> you can use the cert-manager Helm chart directly with tools like Flux, ArgoCD and Anthos. +> Or you can output YAML using `helm template` to generate customized cert-manager installation manifests, +> which can be piped into your preferred deployment tool. -📖 **helm**: You can use [the cert-manager Helm chart](./helm.md) directly with systems like Flux, ArgoCD and Anthos. - -📖 **helm template**: You can use `helm template` to generate customized cert-manager installation manifests. -See [Output YAML using helm template](./helm.md#output-yaml) for more details. -This templated cert-manager manifest can be piped into your preferred deployment tool. +📖 **Continuous Deployment**: Learn [how to automate the installation of cert-manager using tools like Flux and Argo CD](./continuous-deployment-and-gitops.md). diff --git a/content/docs/installation/continuous-deployment-and-gitops.md b/content/docs/installation/continuous-deployment-and-gitops.md new file mode 100644 index 0000000000..45cfc450ea --- /dev/null +++ b/content/docs/installation/continuous-deployment-and-gitops.md @@ -0,0 +1,110 @@ +--- +title: Continuous Deployment +description: Learn how to automate the installation of cert-manager using tools like Flux and Argo CD +--- + +Learn how to automate the installation of cert-manager using tools like Flux and Argo CD. + +## Introduction + +You can use [the cert-manager Helm chart](./helm.md) directly with tools like Flux, ArgoCD and Anthos, +and you can [output YAML using helm template](./helm.md#output-yaml) to generate customized cert-manager installation manifests, +which can be piped into your preferred deployment tool. + +This page contains notes about how to install cert-manager with *some* of these tools. + +> 📢 Please help us improve this page +> by contributing notes or short tutorials about using cert-manager with common GitOps and continuous deployment tools. + +## Using the Flux Helm Controller + +The cert-manager Helm chart can be installed by the [Flux Helm Controller](https://fluxcd.io/flux/components/helm/). + +First create a [`HelmRepository` resource](https://fluxcd.io/flux/components/source/helmrepositories/), +configured with URL of the cert-manager Helm repository. +Then create a [`HelmRelease` resource](https://fluxcd.io/flux/components/helm/helmreleases/), +configured with your desired cert-manager chart values and release. + +Here is an example which installs the latest patch version of the cert-manager 1.12 release, +and then upgrades to the latest patch version of the 1.13 release. + +### Prerequisites + +You'll need the [`flux` CLI](https://fluxcd.io/flux/cmd/) +and a Kubernetes cluster with [Flux installed](https://fluxcd.io/flux/installation/). + +Here's how to quickly install Flux on a [Kind](https://kind.sigs.k8s.io/) cluster: + +```bash +kind create cluster +flux check --pre +flux install +flux check +``` + +### Create a `HelmRepository` resource + +```bash +flux create source helm cert-manager --url https://charts.jetstack.io +``` + +### Create a `HelmRelease` resource + +Put your Helm chart values in a `values.yaml` file. +Use the `installCRDs` value, so that Flux can install and upgrade the CRD resources. + +```yaml +# values.yaml +installCRDs: true +``` + +```bash +flux create helmrelease cert-manager \ + --chart cert-manager \ + --source HelmRepository/cert-manager.flux-system \ + --release-name cert-manager \ + --target-namespace cert-manager \ + --create-target-namespace \ + --crds CreateReplace \ + --values values.yaml \ + --chart-version 1.12.x +``` + +### Updates and Upgrades + +And when you want to upgrade to the cert-manager 1.13 release, +you can simply update the partial semantic version in the chart version: + +```bash +flux create helmrelease cert-manager \ + --chart cert-manager \ + --source HelmRepository/cert-manager.flux-system \ + --release-name cert-manager \ + --target-namespace cert-manager \ + --create-target-namespace \ + --crds CreateReplace \ + --values values.yaml \ + --chart-version 1.13.x +``` + +### Troubleshooting + +Check Flux events and logs for warnings and errors: + +```bash +flux events +flux logs +``` + +Use `cmctl` to check for problems with the cert-manager webhook or CRDs: + +```bash +cmctl check api +cmctl version -o yaml +``` + +Check the cert-manager logs for warnings and errors: + +```bash +kubectl logs -n cert-manager -l app.kubernetes.io/instance=cert-manager --prefix --all-containers +``` diff --git a/content/docs/installation/helm.md b/content/docs/installation/helm.md index 1e911a10bd..870cc78a0d 100644 --- a/content/docs/installation/helm.md +++ b/content/docs/installation/helm.md @@ -286,96 +286,8 @@ support, then please [raise an issue](https://github.com/cert-manager/cert-manager/issues) to discuss. +## Using the Flux Helm Controller -## Install the Helm chart using Flux +The cert-manager Helm chart can be installed and upgraded by the Flux Helm Controller. -The cert-manager Helm chart can be installed by [Flux](https://fluxcd.io/). - -First create a [`HelmRepository` resource](https://fluxcd.io/flux/components/source/helmrepositories/), -configured with URL of the cert-manager Helm repository. -Then create a [`HelmRelease` resource](https://fluxcd.io/flux/components/helm/helmreleases/), -configured with your desired cert-manager chart values and release. - -Here is an example which installs the latest patch version of the cert-manager 1.12 release, -and then upgrades to the latest patch version of the 1.13 release. - -### Prerequisites - -You'll need the [`flux` CLI](https://fluxcd.io/flux/cmd/) -and a Kubernetes cluster with [Flux installed](https://fluxcd.io/flux/installation/). - -Here's how to quickly install Flux on a [Kind](https://kind.sigs.k8s.io/) cluster: - -```bash -kind create cluster -flux check --pre -flux install -flux check -``` - -### Create a `HelmRepository` resource - -```bash -flux create source helm cert-manager --url https://charts.jetstack.io -``` - -### Create a `HelmRelease` resource - -Put your Helm chart values in a `values.yaml` file. -Use the `installCRDs` value, so that Flux can install and upgrade the CRD resources. - -```yaml -# values.yaml -installCRDs: true -``` - -```bash -flux create helmrelease cert-manager \ - --chart cert-manager \ - --source HelmRepository/cert-manager.flux-system \ - --release-name cert-manager \ - --target-namespace cert-manager \ - --create-target-namespace \ - --crds CreateReplace \ - --values values.yaml \ - --chart-version 1.12.x -``` - -### Updates and Upgrades - -And when you want to upgrade to the cert-manager 1.13 release, -you can simply update the partial semantic version in the chart version: - -```bash -flux create helmrelease cert-manager \ - --chart cert-manager \ - --source HelmRepository/cert-manager.flux-system \ - --release-name cert-manager \ - --target-namespace cert-manager \ - --create-target-namespace \ - --crds CreateReplace \ - --values values.yaml \ - --chart-version 1.13.x -``` - -### Troubleshooting - -Check Flux events and logs for warnings and errors: - -```bash -flux events -flux logs -``` - -Use `cmctl` to check for problems with the cert-manager webhook or CRDs: - -```bash -cmctl check api -cmctl version -o yaml -``` - -Check the cert-manager logs for warnings and errors: - -```bash -kubectl logs -n cert-manager -l app.kubernetes.io/instance=cert-manager --prefix --all-containers -``` +> 📖 Read more at [Continuous Deployment: Using the Flux Helm Controller](./continuous-deployment-and-gitops.md). diff --git a/content/docs/manifest.json b/content/docs/manifest.json index 6d81d5889c..dd80672996 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -285,6 +285,10 @@ "title": "c. OperatorHub (OLM)", "path": "/docs/installation/operator-lifecycle-manager.md" }, + { + "title": "d. Continuous Deployment", + "path": "/docs/installation/continuous-deployment-and-gitops.md" + }, { "title": "Configuring Components", "path": "/docs/installation/configuring-components.md" From 02332a26ff443f660cb9b9a0a80981630fa804db Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:33:55 +0200 Subject: [PATCH 239/264] add overview diagrams Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/usage/README.md | 4 + content/docs/usage/certificate.md | 4 + content/docs/usage/certificaterequest.md | 4 + content/docs/usage/csi.md | 4 + content/docs/usage/gateway.md | 4 + content/docs/usage/ingress.md | 4 + content/docs/usage/istio-csr.md | 4 + content/docs/usage/kube-csr.md | 4 + .../request-certificate-cert.svg | 3 + .../request-certificate-cr.svg | 3 + .../request-certificate-csi.svg | 3 + .../request-certificate-csr.svg | 3 + .../request-certificate-gateway.svg | 3 + .../request-certificate-ingress.svg | 3 + .../request-certificate-mesh.svg | 3 + .../request-certificate.drawio | 537 ++++++++++++++++++ .../request-certificate.svg | 3 + 17 files changed, 593 insertions(+) create mode 100644 public/images/request-certificate-overview/request-certificate-cert.svg create mode 100644 public/images/request-certificate-overview/request-certificate-cr.svg create mode 100644 public/images/request-certificate-overview/request-certificate-csi.svg create mode 100644 public/images/request-certificate-overview/request-certificate-csr.svg create mode 100644 public/images/request-certificate-overview/request-certificate-gateway.svg create mode 100644 public/images/request-certificate-overview/request-certificate-ingress.svg create mode 100644 public/images/request-certificate-overview/request-certificate-mesh.svg create mode 100644 public/images/request-certificate-overview/request-certificate.drawio create mode 100644 public/images/request-certificate-overview/request-certificate.svg diff --git a/content/docs/usage/README.md b/content/docs/usage/README.md index c12c16bec2..727b75e4a6 100644 --- a/content/docs/usage/README.md +++ b/content/docs/usage/README.md @@ -3,6 +3,10 @@ title: Requesting Certificates description: 'cert-manager usage: Overview' --- +
              + +
              + Once an [`Issuer`](../configuration/README.md) has been configured, you're ready to issue your first certificate! There are several use cases and methods for requesting certificates through cert-manager: diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index b3998619f6..697d282615 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -6,6 +6,10 @@ description: 'cert-manager usage: Certificates' > **apiVersion:** cert-manager.io/v1 > **kind:** Certificate +
              + +
              + In cert-manager, the `Certificate` resource represents a human readable definition of a certificate request. cert-manager uses this input to generate a private key and [`CertificateRequest`](./certificaterequest.md) resource in order to obtain diff --git a/content/docs/usage/certificaterequest.md b/content/docs/usage/certificaterequest.md index 2c43845d1e..82db0ce585 100644 --- a/content/docs/usage/certificaterequest.md +++ b/content/docs/usage/certificaterequest.md @@ -6,6 +6,10 @@ description: 'cert-manager core concepts: CertificateRequests' > **apiVersion:** cert-manager.io/v1 > **kind:** CertificateRequest +
              + +
              + The `CertificateRequest` is a namespaced resource in cert-manager that is used to request X.509 certificates from an [`Issuer`](../concepts/issuer.md). The resource contains a base64 encoded string of a PEM encoded certificate request which is diff --git a/content/docs/usage/csi.md b/content/docs/usage/csi.md index ff91778eed..913a006b56 100644 --- a/content/docs/usage/csi.md +++ b/content/docs/usage/csi.md @@ -3,6 +3,10 @@ title: CSI Driver description: 'cert-manager usage: CSI driver' --- +
              + +
              + ## Enabling mTLS of Pods using the cert-manager CSI Driver A [Container Storage Interface (CSI) diff --git a/content/docs/usage/gateway.md b/content/docs/usage/gateway.md index 905344a66e..bb4bf0b682 100644 --- a/content/docs/usage/gateway.md +++ b/content/docs/usage/gateway.md @@ -6,6 +6,10 @@ description: 'cert-manager usage: Kubernetes Gateways' > **apiVersion:** gateway.networking.k8s.io/v1alpha2 > **kind:** Gateway +
              + +
              + **FEATURE STATE**: cert-manager 1.5 [alpha]
              diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index b002c62841..042c57e820 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -6,6 +6,10 @@ description: 'cert-manager usage: Kubernetes Ingress' > **apiVersion:** networking.k8s.io/v1 > **kind:** Ingress +
              + +
              + A common use-case for cert-manager is requesting TLS signed certificates to secure your ingress resources. This can be done by simply adding annotations to your `Ingress` resources and cert-manager will facilitate creating the diff --git a/content/docs/usage/istio-csr.md b/content/docs/usage/istio-csr.md index 19ae2eb4bd..c6a601ab6c 100644 --- a/content/docs/usage/istio-csr.md +++ b/content/docs/usage/istio-csr.md @@ -3,6 +3,10 @@ title: Securing Istio Service Mesh description: 'cert-manager usage: Istio and istio-csr' --- +
              + +
              + cert-manager can be integrated with [Istio](https://istio.io) using the project [istio-csr](https://github.com/cert-manager/istio-csr). istio-csr will deploy an agent that is responsible for receiving certificate signing requests for all diff --git a/content/docs/usage/kube-csr.md b/content/docs/usage/kube-csr.md index ac1646db8b..09e6b2bd65 100644 --- a/content/docs/usage/kube-csr.md +++ b/content/docs/usage/kube-csr.md @@ -6,6 +6,10 @@ description: 'cert-manager usage: Kubernetes CertificateSigningRequest resources > **apiVersion:** certificates.k8s.io/v1 > **kind:** CertificateSigningRequest +
              + +
              + Kubernetes has an in-built [CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) resource. This resource is similar to the cert-manager diff --git a/public/images/request-certificate-overview/request-certificate-cert.svg b/public/images/request-certificate-overview/request-certificate-cert.svg new file mode 100644 index 0000000000..15dfe89789 --- /dev/null +++ b/public/images/request-certificate-overview/request-certificate-cert.svg @@ -0,0 +1,3 @@ + + +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Gateway
              Annotated Gateway
              Annotated Ingress
              Annotated Ingress
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-cr.svg b/public/images/request-certificate-overview/request-certificate-cr.svg new file mode 100644 index 0000000000..3fa0fb00ed --- /dev/null +++ b/public/images/request-certificate-overview/request-certificate-cr.svg @@ -0,0 +1,3 @@ + + +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-csi.svg b/public/images/request-certificate-overview/request-certificate-csi.svg new file mode 100644 index 0000000000..40f405ef19 --- /dev/null +++ b/public/images/request-certificate-overview/request-certificate-csi.svg @@ -0,0 +1,3 @@ + + +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              CSI Mount
              CSI Mount
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-csr.svg b/public/images/request-certificate-overview/request-certificate-csr.svg new file mode 100644 index 0000000000..090e9455a9 --- /dev/null +++ b/public/images/request-certificate-overview/request-certificate-csr.svg @@ -0,0 +1,3 @@ + + +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateSigningRequest 
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-gateway.svg b/public/images/request-certificate-overview/request-certificate-gateway.svg new file mode 100644 index 0000000000..9e13f6e328 --- /dev/null +++ b/public/images/request-certificate-overview/request-certificate-gateway.svg @@ -0,0 +1,3 @@ + + +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Gateway
              Annotated Gateway
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-ingress.svg b/public/images/request-certificate-overview/request-certificate-ingress.svg new file mode 100644 index 0000000000..83a2b7624c --- /dev/null +++ b/public/images/request-certificate-overview/request-certificate-ingress.svg @@ -0,0 +1,3 @@ + + +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-mesh.svg b/public/images/request-certificate-overview/request-certificate-mesh.svg new file mode 100644 index 0000000000..84fefd49eb --- /dev/null +++ b/public/images/request-certificate-overview/request-certificate-mesh.svg @@ -0,0 +1,3 @@ + + +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Service mesh
              Service mesh
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate.drawio b/public/images/request-certificate-overview/request-certificate.drawio new file mode 100644 index 0000000000..b2d2fa6e7d --- /dev/null +++ b/public/images/request-certificate-overview/request-certificate.drawio @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/request-certificate-overview/request-certificate.svg b/public/images/request-certificate-overview/request-certificate.svg new file mode 100644 index 0000000000..0dd9f6a50e --- /dev/null +++ b/public/images/request-certificate-overview/request-certificate.svg @@ -0,0 +1,3 @@ + + +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              Text is not SVG - cannot display
              \ No newline at end of file From 26a8bcbfb8385f62ea48b8043f0b6f1098976dd6 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:01:51 +0200 Subject: [PATCH 240/264] improve overview diagrams (decrease height, add legend & try to explain paths) Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .../request-certificate-cert.svg | 2 +- .../request-certificate-cr.svg | 2 +- .../request-certificate-csi.svg | 2 +- .../request-certificate-csr.svg | 2 +- .../request-certificate-gateway.svg | 2 +- .../request-certificate-ingress.svg | 2 +- .../request-certificate-mesh.svg | 2 +- .../request-certificate.drawio | 634 +++++++++++------- .../request-certificate.svg | 2 +- 9 files changed, 413 insertions(+), 237 deletions(-) diff --git a/public/images/request-certificate-overview/request-certificate-cert.svg b/public/images/request-certificate-overview/request-certificate-cert.svg index 15dfe89789..d600200fcb 100644 --- a/public/images/request-certificate-overview/request-certificate-cert.svg +++ b/public/images/request-certificate-overview/request-certificate-cert.svg @@ -1,3 +1,3 @@ -
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Gateway
              Annotated Gateway
              Annotated Ingress
              Annotated Ingress
              Text is not SVG - cannot display
              \ No newline at end of file +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              a
              a
              b
              b
              c
              c
              e
              e
              d
              d
              f
              f
              CertificateSigningRequest 
              CertificateSigningRequest 
              User action
              User action
              Automated action
              Automated action
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              a
              a
              b
              b
              c
              c
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-cr.svg b/public/images/request-certificate-overview/request-certificate-cr.svg index 3fa0fb00ed..d3330fff5a 100644 --- a/public/images/request-certificate-overview/request-certificate-cr.svg +++ b/public/images/request-certificate-overview/request-certificate-cr.svg @@ -1,3 +1,3 @@ -
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              Text is not SVG - cannot display
              \ No newline at end of file +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              a
              a
              b
              b
              c
              c
              e
              e
              d
              d
              f
              f
              CertificateSigningRequest 
              CertificateSigningRequest 
              User action
              User action
              Automated action
              Automated action
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              a
              a
              b
              b
              c
              c
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-csi.svg b/public/images/request-certificate-overview/request-certificate-csi.svg index 40f405ef19..015430196c 100644 --- a/public/images/request-certificate-overview/request-certificate-csi.svg +++ b/public/images/request-certificate-overview/request-certificate-csi.svg @@ -1,3 +1,3 @@ -
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              CSI Mount
              CSI Mount
              Text is not SVG - cannot display
              \ No newline at end of file +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              a
              a
              b
              b
              c
              c
              e
              e
              d
              d
              f
              f
              CertificateSigningRequest 
              CertificateSigningRequest 
              User action
              User action
              Automated action
              Automated action
              CertificateRequest
              CertificateRequest
              CSI Mount
              CSI Mount
              d
              d
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-csr.svg b/public/images/request-certificate-overview/request-certificate-csr.svg index 090e9455a9..89aced625e 100644 --- a/public/images/request-certificate-overview/request-certificate-csr.svg +++ b/public/images/request-certificate-overview/request-certificate-csr.svg @@ -1,3 +1,3 @@ -
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateSigningRequest 
              Text is not SVG - cannot display
              \ No newline at end of file +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              a
              a
              b
              b
              c
              c
              e
              e
              d
              d
              f
              f
              CertificateSigningRequest 
              CertificateSigningRequest 
              User action
              User action
              Automated action
              Automated action
              CertificateSigningRequest 
              CertificateSigningRequest 
              f
              f
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-gateway.svg b/public/images/request-certificate-overview/request-certificate-gateway.svg index 9e13f6e328..7aa0775124 100644 --- a/public/images/request-certificate-overview/request-certificate-gateway.svg +++ b/public/images/request-certificate-overview/request-certificate-gateway.svg @@ -1,3 +1,3 @@ -
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Gateway
              Annotated Gateway
              Text is not SVG - cannot display
              \ No newline at end of file +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              a
              a
              b
              b
              c
              c
              e
              e
              d
              d
              f
              f
              CertificateSigningRequest 
              CertificateSigningRequest 
              User action
              User action
              Automated action
              Automated action
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              c
              c
              Annotated Gateway
              Annotated Gateway
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-ingress.svg b/public/images/request-certificate-overview/request-certificate-ingress.svg index 83a2b7624c..69ebf86f14 100644 --- a/public/images/request-certificate-overview/request-certificate-ingress.svg +++ b/public/images/request-certificate-overview/request-certificate-ingress.svg @@ -1,3 +1,3 @@ -
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Text is not SVG - cannot display
              \ No newline at end of file +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              a
              a
              b
              b
              c
              c
              e
              e
              d
              d
              f
              f
              CertificateSigningRequest 
              CertificateSigningRequest 
              User action
              User action
              Automated action
              Automated action
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              b
              b
              Annotated Ingress
              Annotated Ingress
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate-mesh.svg b/public/images/request-certificate-overview/request-certificate-mesh.svg index 84fefd49eb..ac1b8dda81 100644 --- a/public/images/request-certificate-overview/request-certificate-mesh.svg +++ b/public/images/request-certificate-overview/request-certificate-mesh.svg @@ -1,3 +1,3 @@ -
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              CertificateRequest
              CertificateRequest
              Service mesh
              Service mesh
              Text is not SVG - cannot display
              \ No newline at end of file +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              a
              a
              b
              b
              c
              c
              e
              e
              d
              d
              f
              f
              CertificateSigningRequest 
              CertificateSigningRequest 
              User action
              User action
              Automated action
              Automated action
              CertificateRequest
              CertificateRequest
              Service mesh
              Service mesh
              e
              e
              Text is not SVG - cannot display
              \ No newline at end of file diff --git a/public/images/request-certificate-overview/request-certificate.drawio b/public/images/request-certificate-overview/request-certificate.drawio index b2d2fa6e7d..3815714eb9 100644 --- a/public/images/request-certificate-overview/request-certificate.drawio +++ b/public/images/request-certificate-overview/request-certificate.drawio @@ -1,411 +1,550 @@ - + - + + + + - + - + - + - + - - + + + + + + + - + - - + + + + + + + + + - + - - + + - + - + - - + + - + - + - + + - + + + + + + - - + + + - + + + + + + - - + + + + + + + + - + - - + + + + + + + + + + + - + - - + + + + + + + + + - + - + - + - - - + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - + + - + - + + + + + + - + - + + + + + + - + - - + + + - + - - + + + - + - + - + + + + + + + - + - - + + + + + + + + - + - - + + + - + + + + + + - - + + + + + + + + + + - + - - + + + - - + + + + - + - + - - - + + + + + + + + + - + - + + + + - - + + - - - + + + + - - + + + + - - + + - + + - + + - - - - - + + - - - - - - - - - - + + - - + + - - - - + - + - - + + - - - - + - + + - - - - - - + + + - + - - + + + + - - - - - - + + + - + - - + + + + + + + + + - - + + + + + - - + + - + + - + + - - + + - - - - + + - - + + - - - + - + - - + + - - - - + - - + + - + + + + + + - + - - - + + + + - - + + + + - + - - + + - - + + + + + + + + + + + + + - - - @@ -416,120 +555,157 @@ - + + + + - - + + + - - + + - + - - + + + + + - - + + + + - - + + - + - - + + + - - + + - + - - + + + - - + + + + - - + + - + + - + + - - + + - - - - - + + + + + + + + - + - - + + + + + + + + + - - + + - - + + - + + - + + - - + + - - - - - + + + + + + + + - + - - + + + + + + + + + - - + + diff --git a/public/images/request-certificate-overview/request-certificate.svg b/public/images/request-certificate-overview/request-certificate.svg index 0dd9f6a50e..4e7755fc1c 100644 --- a/public/images/request-certificate-overview/request-certificate.svg +++ b/public/images/request-certificate-overview/request-certificate.svg @@ -1,3 +1,3 @@ -
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              CertificateSigningRequest 
              CertificateSigningRequest 
              Text is not SVG - cannot display
              \ No newline at end of file +
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              a
              a
              b
              b
              c
              c
              e
              e
              d
              d
              f
              f
              CertificateSigningRequest 
              CertificateSigningRequest 
              User action
              User action
              Automated action
              Automated action
              CertificateRequest
              CertificateRequest
              Certificate
              Certificate
              Annotated Ingress
              Annotated Ingress
              Annotated Gateway
              Annotated Gateway
              Service mesh
              Service mesh
              CSI Mount
              CSI Mount
              a
              a
              b
              b
              c
              c
              e
              e
              d
              d
              f
              f
              CertificateSigningRequest 
              CertificateSigningRequest 
              Text is not SVG - cannot display
              \ No newline at end of file From b118d54df5034f80c65556f6aa960789a9081d0b Mon Sep 17 00:00:00 2001 From: Carsten Hiort Date: Tue, 7 Nov 2023 08:46:28 +0100 Subject: [PATCH 241/264] Fix broken link due to new structure of Knative docs website (https://github.com/knative/docs/commit/6d938133f7810d1ae588bee103a488ecbf16c2a8) Signed-off-by: Carsten Hiort --- content/docs/usage/README.md | 2 +- content/v1.0-docs/usage/README.md | 2 +- content/v1.1-docs/usage/README.md | 2 +- content/v1.10-docs/usage/README.md | 2 +- content/v1.11-docs/usage/README.md | 2 +- content/v1.12-docs/usage/README.md | 2 +- content/v1.13-docs/usage/README.md | 2 +- content/v1.2-docs/usage/README.md | 2 +- content/v1.3-docs/usage/README.md | 2 +- content/v1.4-docs/usage/README.md | 2 +- content/v1.5-docs/usage/README.md | 2 +- content/v1.6-docs/usage/README.md | 2 +- content/v1.7-docs/usage/README.md | 2 +- content/v1.8-docs/usage/README.md | 2 +- content/v1.9-docs/usage/README.md | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/content/docs/usage/README.md b/content/docs/usage/README.md index 727b75e4a6..59f605379a 100644 --- a/content/docs/usage/README.md +++ b/content/docs/usage/README.md @@ -20,7 +20,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.0-docs/usage/README.md b/content/v1.0-docs/usage/README.md index 6cdb93f729..5013606980 100644 --- a/content/v1.0-docs/usage/README.md +++ b/content/v1.0-docs/usage/README.md @@ -19,7 +19,7 @@ cases and methods for requesting certificates through cert-manager: Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.1-docs/usage/README.md b/content/v1.1-docs/usage/README.md index e9c1994527..246126393a 100644 --- a/content/v1.1-docs/usage/README.md +++ b/content/v1.1-docs/usage/README.md @@ -19,7 +19,7 @@ cases and methods for requesting certificates through cert-manager: Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.10-docs/usage/README.md b/content/v1.10-docs/usage/README.md index b0d93801a9..451db2ed9d 100644 --- a/content/v1.10-docs/usage/README.md +++ b/content/v1.10-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.11-docs/usage/README.md b/content/v1.11-docs/usage/README.md index b0d93801a9..451db2ed9d 100644 --- a/content/v1.11-docs/usage/README.md +++ b/content/v1.11-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.12-docs/usage/README.md b/content/v1.12-docs/usage/README.md index b0d93801a9..451db2ed9d 100644 --- a/content/v1.12-docs/usage/README.md +++ b/content/v1.12-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.13-docs/usage/README.md b/content/v1.13-docs/usage/README.md index f02af619dc..c971d2d2c5 100644 --- a/content/v1.13-docs/usage/README.md +++ b/content/v1.13-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.2-docs/usage/README.md b/content/v1.2-docs/usage/README.md index 6cdb93f729..5013606980 100644 --- a/content/v1.2-docs/usage/README.md +++ b/content/v1.2-docs/usage/README.md @@ -19,7 +19,7 @@ cases and methods for requesting certificates through cert-manager: Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.3-docs/usage/README.md b/content/v1.3-docs/usage/README.md index 6cdb93f729..5013606980 100644 --- a/content/v1.3-docs/usage/README.md +++ b/content/v1.3-docs/usage/README.md @@ -19,7 +19,7 @@ cases and methods for requesting certificates through cert-manager: Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.4-docs/usage/README.md b/content/v1.4-docs/usage/README.md index f987f3bc09..ebe397890c 100644 --- a/content/v1.4-docs/usage/README.md +++ b/content/v1.4-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.5-docs/usage/README.md b/content/v1.5-docs/usage/README.md index f987f3bc09..ebe397890c 100644 --- a/content/v1.5-docs/usage/README.md +++ b/content/v1.5-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.6-docs/usage/README.md b/content/v1.6-docs/usage/README.md index f987f3bc09..ebe397890c 100644 --- a/content/v1.6-docs/usage/README.md +++ b/content/v1.6-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.7-docs/usage/README.md b/content/v1.7-docs/usage/README.md index f987f3bc09..ebe397890c 100644 --- a/content/v1.7-docs/usage/README.md +++ b/content/v1.7-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.8-docs/usage/README.md b/content/v1.8-docs/usage/README.md index f987f3bc09..ebe397890c 100644 --- a/content/v1.8-docs/usage/README.md +++ b/content/v1.8-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of diff --git a/content/v1.9-docs/usage/README.md b/content/v1.9-docs/usage/README.md index b0d93801a9..451db2ed9d 100644 --- a/content/v1.9-docs/usage/README.md +++ b/content/v1.9-docs/usage/README.md @@ -16,7 +16,7 @@ There are several use cases and methods for requesting certificates through cert - [Integration with Garden](https://docs.garden.io/guides/cert-manager-integration): Garden is a developer tool for developing Kubernetes applications which has first class support for integrating cert-manager. -- [Securing Knative](https://knative.dev/docs/serving/using-auto-tls/): Secure +- [Securing Knative](https://knative.dev/docs/serving/encryption/enabling-automatic-tls-certificate-provisioning/): Secure your Knative services with trusted HTTPS certificates. - [Enable mTLS on Pods with CSI](./csi.md): Using the cert-manager CSI driver to provide unique keys and certificates that share the lifecycle of From cb178d97fcffa5ea193eeb63b5e9e7adb8813142 Mon Sep 17 00:00:00 2001 From: Thomas Spear Date: Tue, 7 Nov 2023 11:19:20 -0600 Subject: [PATCH 242/264] Clarify that it is the webhook, not the CA Injector, which creates the secret with the webhook's root CA certificate Signed-off-by: Thomas Spear --- content/docs/concepts/webhook.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/concepts/webhook.md b/content/docs/concepts/webhook.md index 884e92d7c8..05345edf43 100644 --- a/content/docs/concepts/webhook.md +++ b/content/docs/concepts/webhook.md @@ -35,7 +35,7 @@ cert-manager controller and CA injector components. In order for the API server to communicate with the webhook component, the webhook requires a TLS certificate that the apiserver is configured to trust. -The [`cainjector`](./ca-injector.md) creates `secret/cert-manager-webhook-ca`, a self-signed root CA certificate which is used to sign certificates for the webhook pod. +The webhook creates `secret/cert-manager-webhook-ca` in the namespace where the webhook is deployed. This secret contains a self-signed root CA certificate which is used to sign certificates for the webhook pod in order to fulfill this requirement. Then the webhook can be configured with either From 20644136b7088a58ff707943a0adb10641abbfd7 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Wed, 8 Nov 2023 16:26:30 -0600 Subject: [PATCH 243/264] bump numbers on homepage Signed-off-by: Ashley Davis --- content/pages/homepage.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/pages/homepage.mdx b/content/pages/homepage.mdx index e0fa2239b4..0e76afe1e5 100644 --- a/content/pages/homepage.mdx +++ b/content/pages/homepage.mdx @@ -10,15 +10,15 @@ export const meta = { stats: [ { icon: 'github', - description: '10,000+ GitHub Stars' + description: '11,000+ GitHub Stars' }, { icon: 'slack', - description: '7000+ Slack members' + description: '8000+ Slack members' }, { icon: 'downloads', - description: '1 million+ daily downloads' + description: '5 million+ daily downloads' } ], topCta: { From 088088d7cc5cf8f1bf9627ec8dd5d4b6a0462f8e Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 9 Nov 2023 09:38:22 +0100 Subject: [PATCH 244/264] docs: Small typo on best practice: is should have been us Signed-off-by: Cees-Jan Kiewiet --- content/docs/installation/best-practice.md | 2 +- content/v1.13-docs/installation/best-practice.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 75558131ac..95948142b3 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -320,7 +320,7 @@ but first read the background information below. > 📢 The controller liveness probe is a new feature in cert-manager release 1.12 > and it is disabled by default, as a precaution, in case it causes problems in the field. > [Please get in touch](../contributing/README.md) -> and tell is if you have enabled the controller liveness probe in production +> and tell us if you have enabled the controller liveness probe in production > and tell us whether you would like it to be turned on by default. > Tell us about any circumstances where the controller has become stuck > and where the liveness probe has been necessary to automatically restart the process. diff --git a/content/v1.13-docs/installation/best-practice.md b/content/v1.13-docs/installation/best-practice.md index 2db8f3b153..651f9406bd 100644 --- a/content/v1.13-docs/installation/best-practice.md +++ b/content/v1.13-docs/installation/best-practice.md @@ -50,7 +50,7 @@ but first read the background information below. > 📢 The controller liveness probe is a new feature in cert-manager release 1.12 > and it is disabled by default, as a precaution, in case it causes problems in the field. > [Please get in touch](../contributing/README.md) -> and tell is if you have enabled the controller liveness probe in production +> and tell us if you have enabled the controller liveness probe in production > and tell us whether you would like it to be turned on by default. > Tell us about any circumstances where the controller has become stuck > and where the liveness probe has been necessary to automatically restart the process. From 1c4461345770b1988557faf0d2ba7c5549c89ff3 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 9 Nov 2023 09:45:14 +0100 Subject: [PATCH 245/264] docs: Removed some repetition from liveness probe notes Signed-off-by: Cees-Jan Kiewiet --- content/docs/installation/best-practice.md | 4 ++-- content/v1.13-docs/installation/best-practice.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 95948142b3..6f57f4e574 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -321,8 +321,8 @@ but first read the background information below. > and it is disabled by default, as a precaution, in case it causes problems in the field. > [Please get in touch](../contributing/README.md) > and tell us if you have enabled the controller liveness probe in production -> and tell us whether you would like it to be turned on by default. -> Tell us about any circumstances where the controller has become stuck +> and whether you would like it to be turned on by default. +> Please also include any circumstances where the controller has become stuck > and where the liveness probe has been necessary to automatically restart the process. The liveness probe for the cert-manager controller is an HTTP probe which connects diff --git a/content/v1.13-docs/installation/best-practice.md b/content/v1.13-docs/installation/best-practice.md index 651f9406bd..be6b1c3dfa 100644 --- a/content/v1.13-docs/installation/best-practice.md +++ b/content/v1.13-docs/installation/best-practice.md @@ -51,8 +51,8 @@ but first read the background information below. > and it is disabled by default, as a precaution, in case it causes problems in the field. > [Please get in touch](../contributing/README.md) > and tell us if you have enabled the controller liveness probe in production -> and tell us whether you would like it to be turned on by default. -> Tell us about any circumstances where the controller has become stuck +> and whether you would like it to be turned on by default. +> Please also include any circumstances where the controller has become stuck > and where the liveness probe has been necessary to automatically restart the process. The liveness probe for the cert-manager controller is an HTTP probe which connects From cfbd225039013b73a1575990a624b6a88a584e2a Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Thu, 9 Nov 2023 13:23:27 +0000 Subject: [PATCH 246/264] Link to the official Flux example repo See https://github.com/fluxcd/flux2/discussions/3009#discussioncomment-7508826 Signed-off-by: Richard Wall --- .../docs/installation/continuous-deployment-and-gitops.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/content/docs/installation/continuous-deployment-and-gitops.md b/content/docs/installation/continuous-deployment-and-gitops.md index 45cfc450ea..b6f177fe6b 100644 --- a/content/docs/installation/continuous-deployment-and-gitops.md +++ b/content/docs/installation/continuous-deployment-and-gitops.md @@ -28,6 +28,12 @@ configured with your desired cert-manager chart values and release. Here is an example which installs the latest patch version of the cert-manager 1.12 release, and then upgrades to the latest patch version of the 1.13 release. +> ⚠️ This is a simple example which may not be suitable for production use. +> You should also refer to the [official Flux example repo](https://github.com/fluxcd/flux2-kustomize-helm-example), +> where cert-manager is now fully integrated. +> It shows how to deploy ClusterIssuer resources in the right order, +> after cert-manager CRDs and controller have been installed. + ### Prerequisites You'll need the [`flux` CLI](https://fluxcd.io/flux/cmd/) From 352805e8cbffbe11fbadc7a859e636cf1d6c1351 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Thu, 9 Nov 2023 13:24:41 +0000 Subject: [PATCH 247/264] Remove unnecessary --crds CreateReplace argument See https://github.com/fluxcd/flux2/discussions/3009#discussioncomment-7510213 Signed-off-by: Richard Wall --- content/docs/installation/continuous-deployment-and-gitops.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/content/docs/installation/continuous-deployment-and-gitops.md b/content/docs/installation/continuous-deployment-and-gitops.md index b6f177fe6b..aaa3a07886 100644 --- a/content/docs/installation/continuous-deployment-and-gitops.md +++ b/content/docs/installation/continuous-deployment-and-gitops.md @@ -71,7 +71,6 @@ flux create helmrelease cert-manager \ --release-name cert-manager \ --target-namespace cert-manager \ --create-target-namespace \ - --crds CreateReplace \ --values values.yaml \ --chart-version 1.12.x ``` @@ -88,7 +87,6 @@ flux create helmrelease cert-manager \ --release-name cert-manager \ --target-namespace cert-manager \ --create-target-namespace \ - --crds CreateReplace \ --values values.yaml \ --chart-version 1.13.x ``` From ecfee0477081d4bdc4ff2e25198172b04ae0a59a Mon Sep 17 00:00:00 2001 From: Antonin Date: Tue, 14 Nov 2023 17:30:17 +0100 Subject: [PATCH 248/264] Added horizon-issuer as an external issuer Signed-off-by: Antonin --- content/docs/configuration/external.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/docs/configuration/external.md b/content/docs/configuration/external.md index 4cad250c44..95b6264ad3 100644 --- a/content/docs/configuration/external.md +++ b/content/docs/configuration/external.md @@ -47,6 +47,7 @@ These external issuers are known to support and honor [approval](https://cert-ma - [tcs-issuer](https://github.com/intel/trusted-certificate-issuer) Requests certificates signed securely using [Intel's SGX technology](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/overview.html). - [ejbca-issuer](https://github.com/Keyfactor/ejbca-cert-manager-issuer): Request certificates from [EJBCA](https://www.ejbca.org/). - [command-issuer](https://github.com/Keyfactor/command-cert-manager-issuer): Request certificates from [Keyfactor Command](https://www.keyfactor.com/products/command/). +- [horizon-issuer](https://github.com/evertrust/horizon-issuer): Request certificates from [EVERTRUST Horizon](https://evertrust.fr/horizon). ## Building New External Issuers From a6fb75d3e100e7bd317ebaa592187107b1342d80 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 7 Nov 2023 18:08:12 +0000 Subject: [PATCH 249/264] Network policy recommendations Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> Signed-off-by: Richard Wall --- content/docs/installation/best-practice.md | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/content/docs/installation/best-practice.md b/content/docs/installation/best-practice.md index 75558131ac..dee8eac69d 100644 --- a/content/docs/installation/best-practice.md +++ b/content/docs/installation/best-practice.md @@ -22,6 +22,99 @@ are designed for backwards compatibility rather than for best practice or maximu You may find that the default resources do not comply with the security policy on your Kubernetes cluster and in that case you can modify the installation configuration using Helm chart values to override the defaults. +## Network Requirements and Network Policy + +The network requirements of each cert-manager Pod are summarized below. +Some network requirements depend on specific Issuer / ClusterIssuer configurations +and / or specific configuration options. + +When you have understood the network requirements of **your** cert-manager installation, +you should consider implementing a "least privilege" network policy, +using a [Kubernetes Network (CNI) Plugin](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) such as [Calico](https://www.tigera.io/project-calico/). + +The network policy should prevent untrusted clients from connecting to the cert-manager Pods +and it should prevent cert-manager from connecting to untrusted servers. + +An example of this recommendation is found in the Calico Documentation: +> We recommend creating an implicit default deny policy for your Kubernetes pods, regardless of whether you use Calico or Kubernetes network policy. This ensures that unwanted traffic is denied by default. +> +> 📖 [Calico Best practice: implicit default deny policy](https://docs.tigera.io/calico/latest/network-policy/get-started/kubernetes-default-deny#best-practice-implicit-default-deny-policy). + +You can use the Kubernetes builtin `NetworkPolicy` resource, +which is portable because it is recognized by any of the [Kubernetes Network (CNI) Plugins](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/). +Or you may prefer to use the custom resources provided by your CNI software. + +> 📖 Learn about the [Kubernetes builtin NetworkPolicy API](https://kubernetes.io/docs/concepts/services-networking/network-policies/) +> and see [some example policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/#default-policies). + +### Network Requirements + +Here is an overview of the network requirements: + +1. **UDP / TCP: cert-manager (all) -> Kubernetes DNS**: + All cert-manager components perform UDP DNS queries for both cluster and external domain names. + Some DNS queries may use TCP. + +1. **TCP: Kubernetes (API server) -> cert-manager (webhook)**: + The Kubernetes API server establishes HTTPS connections to the [cert-manager webhook component](../concepts/webhook.md). + Read the cert-manager [webhook troubleshooting guide](../troubleshooting/webhook.md) + to understand the webhook networking requirements. + +1. **TCP: cert-manager (webhook, controller, cainjector, startupapicheck) -> Kubernetes API server**: + The cert-manager webhook, controller, cainjector and startupapicheck + establish HTTPS connections to the Kubernetes API server, + to interact with cert-manager custom resources and Kubernetes resources. + The cert-manager webhook is a special case; + it connects to the Kubernetes API server to use the `SubjectAccessReview` API, + to verify clients attempting to modify `Approved` or `Denied` conditions of `CertificateRequest` resources. + +1. **TCP: cert-manager (controller) -> HashiCorp Vault (authentication and resource API endpoints)**: + The cert-manager controller may establish HTTPS connections to one or more Vault API endpoints, + if you are using the [Vault Issuer](../configuration/vault.md). + The target host and port of the Vault endpoints + are configured in Issuer or ClusterIssuer resources. + +1. **TCP: cert-manager (controller) -> Venafi TLS Protect (authentication and resource API endpoints)**: + The cert-manager controller may establish HTTPS connections to one or more Venafi API endpoints, + if you are using the [Venafi Issuer](../configuration/venafi.md). + The target host and port of the Venafi API endpoints + are configured in Issuer or ClusterIssuer resources. + +1. **TCP: cert-manager (controller) -> DNS API endpoints (for ACME DNS01)**: + The cert-manager controller may establish HTTPS connections to DNS API endpoints such as Amazon Route53, + and to any associated authentication endpoints, + if you are using the [ACME Issuer with DNS01 solvers](../configuration/acme/dns01/README.md#supported-dns01-providers). + +1. **UDP / TCP: cert-manager (controller) -> External DNS**: + If you use the ACME Issuer, the cert-manager controller may send + DNS queries to recursive DNS servers, + as part of the ACME challenge self-check process. + It does this to ensure that the DNS01 or HTTP01 challenge is resolvable, + before asking the ACME server to perform its checks. + + In the case of DNS01 it may also perform a series of DNS queries to authoritative DNS servers, + to compute the DNS zone in which to add the DNS01 challenge record. + In the case of DNS01, cert-manager also [supports DNS over HTTPS](../releases/release-notes/release-notes-1.13.md#dns-over-https-doh-support). + + You can choose the host and port of the DNS servers, using the following [controller flags](../cli/controller.md): + `--acme-http01-solver-nameservers`, + `--dns01-recursive-nameservers`, and + `--dns01-recursive-nameservers-only`. + +1. **TCP: ACME (Let's Encrypt) -> cert-manager (acmesolver)**: + If you use an ACME Issuer configured for HTTP01, + cert-manager will deploy an `acmesolver` Pod, a Service and an Ingress (or Gateway API) resource + in the namespace of the Issuer + or in the cert-manager namespace if it is a ClusterIssuer. + The ACME implementation will establish an HTTP connection to this Pod via your chosen ingress load balancer, + so your network policy must allow this. + + > ℹ️ The acmesolver Pod **does not** require access to the Kubernetes API server. + +1. **TCP: Metrics Server -> cert-manager (controller)**: + The cert-manager controller has a metrics server which listens for HTTP connections on TCP port 9402. + Create a network policy which allows access to this service from your chosen metrics collector. + ## Isolate cert-manager on dedicated node pools cert-manager is a cluster scoped operator and you should treat it as part of your platform's control plane. From 25745371a284e49f1a6c19495c877aecf9de1691 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 15 Nov 2023 17:11:41 +0000 Subject: [PATCH 250/264] Make Peter Fiddes (@hawksight) a reviewer Thanks for all your documentation reviews Peter, for example: * https://github.com/cert-manager/website/pull/1344 * https://github.com/cert-manager/website/pull/1338 * https://github.com/cert-manager/website/pull/1331 We'd like you to be able to `lgtm` future PRs, so we're adding you to the reviewers list. Signed-off-by: Richard Wall --- OWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OWNERS b/OWNERS index 36d1cae66f..c3e3a5f93d 100644 --- a/OWNERS +++ b/OWNERS @@ -7,3 +7,5 @@ approvers: - SgtCoDFish - wallrj - inteon +reviewers: +- hawksight From 9f369cb7133342e49fa5a65313cf40b39db82316 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:34:07 +0100 Subject: [PATCH 251/264] add v1.13 upgrade note saying you should upgrade to v1.12 first Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/releases/upgrading/upgrading-1.12-1.13.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/content/docs/releases/upgrading/upgrading-1.12-1.13.md b/content/docs/releases/upgrading/upgrading-1.12-1.13.md index 859ff22987..927e1f9383 100644 --- a/content/docs/releases/upgrading/upgrading-1.12-1.13.md +++ b/content/docs/releases/upgrading/upgrading-1.12-1.13.md @@ -5,14 +5,16 @@ description: 'cert-manager installation: Upgrading v1.12 to v1.13' When upgrading cert-manager from 1.12 to 1.13, in few cases you might need to take additional steps to ensure a smooth upgrade: -1. BREAKING: If you deploy cert-manager using helm and have `.featureGates` value set, the features defined +1. **IMPORTANT NOTE**: Before upgrading to v1.13, upgrade to a v1.12+ version first. Otherwise, you might unexpectedly experience certificates to be re-issued (see https://github.com/cert-manager/cert-manager/issues/6494#issuecomment-1816112309) + +2. BREAKING: If you deploy cert-manager using helm and have `.featureGates` value set, the features defined there will no longer be passed to cert-manager webhook, only to cert-manager controller. Use `webhook.featureGates` field instead to define features to be enabled on webhook. (https://github.com/cert-manager/cert-manager/pull/6093, https://github.com/irbekrm) -2. Potentially breaking: If you were, for some reason, passing cert-manager controller's features to webhook's --feature-gates flag, +3. Potentially breaking: If you were, for some reason, passing cert-manager controller's features to webhook's --feature-gates flag, this will now break (unless the webhook actually has a feature by that name). (https://github.com/cert-manager/cert-manager/pull/6093, https://github.com/irbekrm) -3. Potentially breaking: Webhook validation of CertificateRequest resources is stricter now: all `KeyUsages` and `ExtendedKeyUsages` must be defined directly in the CertificateRequest resource, the encoded CSR can never contain more usages that defined there. (https://github.com/cert-manager/cert-manager/pull/6182, https://github.com/inteon) +4. Potentially breaking: Webhook validation of CertificateRequest resources is stricter now: all `KeyUsages` and `ExtendedKeyUsages` must be defined directly in the CertificateRequest resource, the encoded CSR can never contain more usages that defined there. (https://github.com/cert-manager/cert-manager/pull/6182, https://github.com/inteon) ## Next Steps From dcf5d885dc96d955ab92674d9993f27d8220094a Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:22:35 +0100 Subject: [PATCH 252/264] Update content/docs/releases/upgrading/upgrading-1.12-1.13.md Co-authored-by: Ashley Davis Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/releases/upgrading/upgrading-1.12-1.13.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/releases/upgrading/upgrading-1.12-1.13.md b/content/docs/releases/upgrading/upgrading-1.12-1.13.md index 927e1f9383..6cd4d4e503 100644 --- a/content/docs/releases/upgrading/upgrading-1.12-1.13.md +++ b/content/docs/releases/upgrading/upgrading-1.12-1.13.md @@ -5,7 +5,7 @@ description: 'cert-manager installation: Upgrading v1.12 to v1.13' When upgrading cert-manager from 1.12 to 1.13, in few cases you might need to take additional steps to ensure a smooth upgrade: -1. **IMPORTANT NOTE**: Before upgrading to v1.13, upgrade to a v1.12+ version first. Otherwise, you might unexpectedly experience certificates to be re-issued (see https://github.com/cert-manager/cert-manager/issues/6494#issuecomment-1816112309) +1. **IMPORTANT NOTE**: If upgrading from a version below v1.12, upgrade to the latest v1.12 release before upgrading to v1.13. Otherwise, some certificates may be unexpectedly re-issued (see https://github.com/cert-manager/cert-manager/issues/6494#issuecomment-1816112309) 2. BREAKING: If you deploy cert-manager using helm and have `.featureGates` value set, the features defined there will no longer be passed to cert-manager webhook, only to cert-manager controller. Use `webhook.featureGates` field From b06591bd3d53c69271b5ce6e5b7a9aa777530a82 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 25 Oct 2023 15:51:42 +0200 Subject: [PATCH 253/264] replace external issuers page with list of ALL issuers, also add ranks to indicate the quality of issuers and their documentation Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .spelling | 5 + content/docs/configuration/external.md | 54 ------- content/docs/configuration/issuers.md | 111 ++++++++++++++ content/docs/contributing/external-issuers.md | 4 +- content/docs/manifest.json | 137 +++++++++--------- .../policy/approval/approver-policy/README.md | 2 +- public/_redirects | 3 + styles/global.scss | 18 +++ 8 files changed, 211 insertions(+), 123 deletions(-) delete mode 100644 content/docs/configuration/external.md create mode 100644 content/docs/configuration/issuers.md diff --git a/.spelling b/.spelling index 33596e7790..15673739a4 100644 --- a/.spelling +++ b/.spelling @@ -666,6 +666,11 @@ gateway.networking.k8s.io networking.k8s.io certificates.k8s.io +venafi-enhanced-issuer +venafi-issuer +adcs-issuer +cfssl-issuer + # TEMPORARY # these are temporarily ignored because the spellchecker # has difficulty detecting that they're in comments or links diff --git a/content/docs/configuration/external.md b/content/docs/configuration/external.md deleted file mode 100644 index 95b6264ad3..0000000000 --- a/content/docs/configuration/external.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: External -description: 'cert-manager configuration: External Issuers' ---- - -cert-manager supports external `Issuer` types. While external issuers are not -implemented in the main cert-manager repository, they are otherwise treated the -same as any other issuer. - -External issuers are typically deployed as a pod which is configured -to watch for `CertificateRequest` resources in the cluster whose `issuerRef` -matches the name of the issuer. External issuers exist outside of the -`cert-manager.io` group. - -Installation for each issuer may differ; check the documentation for each -external issuer for more details on installing, configuring and using it. - -## Known External Issuers - -If you've created an external issuer which you'd like to share, -[raise a Pull Request](https://github.com/cert-manager/website/pulls) to have -it added here! - -These external issuers are known to support and honor [approval](https://cert-manager.io/docs/concepts/certificaterequest/#approval). - -- [kms-issuer](https://github.com/Skyscanner/kms-issuer): Requests - certificates signed using an [AWS KMS](https://aws.amazon.com/kms/) asymmetric key. -- [aws-privateca-issuer](https://github.com/cert-manager/aws-privateca-issuer): Requests - certificates from [AWS Private Certificate Authority](https://aws.amazon.com/certificate-manager/private-certificate-authority/) - for cloud native/hybrid environments. -- [google-cas-issuer](https://github.com/jetstack/google-cas-issuer): Used - to request certificates signed by private CAs managed by the - [Google Cloud Certificate Authority Service](https://cloud.google.com/certificate-authority-service/). -- [origin-ca-issuer](https://github.com/cloudflare/origin-ca-issuer): Used - to request certificates signed by - [Cloudflare Origin CA](https://developers.cloudflare.com/ssl/origin-configuration/origin-ca) - to enable TLS between Cloudflare edge and your Kubernetes workloads. -- [step-issuer](https://github.com/smallstep/step-issuer): Requests - certificates from the [Smallstep](https://smallstep.com) [Certificate Authority server](https://github.com/smallstep/certificates). -- [freeipa-issuer](https://github.com/guilhem/freeipa-issuer): Requests - certificates signed by [FreeIPA](https://www.freeipa.org). -- [ADCS Issuer](https://github.com/nokia/adcs-issuer): Requests - certificates signed by [Microsoft Active Directory Certificate Service](https://docs.microsoft.com/en-us/windows-server/networking/core-network-guide/cncg/server-certs/install-the-certification-authority). - [NOT MAINTAINED] -- [CFSSL Issuer](https://gerrit.wikimedia.org/r/plugins/gitiles/operations/software/cfssl-issuer/): Request certificates signed by a [CFSSL](https://github.com/cloudflare/cfssl) `multirootca` instance. -- [ncm-issuer](https://github.com/nokia/ncm-issuer): Requests certificates from the [Nokia](https://www.nokia.com/) [Netguard Certificate Manager](https://www.nokia.com/networks/security-portfolio/netguard/certificate-manager) -- [tcs-issuer](https://github.com/intel/trusted-certificate-issuer) Requests certificates signed securely using [Intel's SGX technology](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/overview.html). -- [ejbca-issuer](https://github.com/Keyfactor/ejbca-cert-manager-issuer): Request certificates from [EJBCA](https://www.ejbca.org/). -- [command-issuer](https://github.com/Keyfactor/command-cert-manager-issuer): Request certificates from [Keyfactor Command](https://www.keyfactor.com/products/command/). -- [horizon-issuer](https://github.com/evertrust/horizon-issuer): Request certificates from [EVERTRUST Horizon](https://evertrust.fr/horizon). - -## Building New External Issuers - -If you're interested in building a new external issuer, check the [development documentation](../contributing/external-issuers.md). diff --git a/content/docs/configuration/issuers.md b/content/docs/configuration/issuers.md new file mode 100644 index 0000000000..a6d925525c --- /dev/null +++ b/content/docs/configuration/issuers.md @@ -0,0 +1,111 @@ +--- +title: Issuers +description: 'cert-manager configuration: Issuers' +--- + +The following list contains all known cert-manager issuer integrations. + +
              +| Tier | Controller | Docs | Issuer | cert-manager
              version used
              in tutorial[^1] | Released within
              3 months[^2] | Is Open Source | +|------|------------|------|--------|--------|--------|--------| +| 🥇 | acme-issuer (in-tree) | [📄][config:acme-issuer] | [ACME](https://datatracker.ietf.org/doc/html/rfc8555) | [latest][production:acme-issuer] | [✔️][release:cert-manager] | ✔️ | +| 🥇 | venafi-enhanced-issuer | [📄][config:venafi-enhanced-issuer] | [Venafi TLS Protect](https://venafi.com/tls-protect/) | [v1.12.1][production:venafi-enhanced-issuer] | [✔️][release:venafi-enhanced-issuer] | ❌ | +| 🥈 | aws-privateca-issuer | [📄][config:aws-privateca-issuer] | [AWS Private Certificate Authority](https://aws.amazon.com/certificate-manager/private-certificate-authority/) | - | [✔️][release:aws-privateca-issuer] | ✔️ | +| 🥈 | ca-issuer (in-tree) | [📄][config:ca-issuer] | CA issuer | - | [✔️][release:cert-manager] | ✔️ | +| 🥈 | command-issuer | [📄][config:command-issuer] | [Keyfactor Command](https://www.keyfactor.com/products/command/) | - | [✔️][release:command-issuer] | ✔️ | +| 🥈 | ejbca-issuer | [📄][config:ejbca-issuer] | [EJBCA](https://www.ejbca.org/) | - | [✔️][release:ejbca-issuer] | ✔️ | +| 🥈 | google-cas-issuer | [📄][config:google-cas-issuer] | [Google Cloud Certificate
              Authority Service](https://cloud.google.com/certificate-authority-service/) | - | [✔️][release:google-cas-issuer] | ✔️ | +| 🥈 | ncm-issuer | [📄][config:ncm-issuer] | [Nokia Netguard Certificate Manager](https://www.nokia.com/networks/security-portfolio/netguard/certificate-manager) | - | [✔️][release:ncm-issuer] | ✔️ | +| 🥈 | selfsigned-issuer (in-tree) | [📄][config:selfsigned-issuer] | Self-Signed issuer | - | [✔️][release:cert-manager] | ✔️ | +| 🥈 | step-issuer | [📄][config:step-issuer] | [Certificate Authority server](https://github.com/smallstep/certificates) | - | [✔️][release:step-issuer] | ✔️ | +| 🥈 | tcs-issuer | [📄][config:tcs-issuer] | [Intel's SGX technology](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/overview.html) | - | [✔️][release:tcs-issuer] | ✔️ | +| 🥈 | vault-issuer (in-tree) | [📄][config:vault-issuer] | [HashiCorp Vault](https://www.vaultproject.io/) | - | [✔️][release:cert-manager] | ✔️ | +| 🥈 | venafi-issuer (in-tree) | [📄][config:venafi-issuer] | [Venafi TLS Protect](https://venafi.com/tls-protect/) | - | [✔️][release:cert-manager] | ✔️ | +| 🥉 | adcs-issuer | [📄][config:adcs-issuer] | [Microsoft Active Directory
              Certificate Service](https://docs.microsoft.com/en-us/windows-server/networking/core-network-guide/cncg/server-certs/install-the-certification-authority) | - | [❌][release:adcs-issuer] | ✔️ | +| 🥉 | cfssl-issuer | [📄][config:cfssl-issuer] | [CFSSL](https://github.com/cloudflare/cfssl) | - | [❌][release:cfssl-issuer] | ✔️ | +| 🥉 | freeipa-issuer | [📄][config:freeipa-issuer] | [FreeIPA](https://www.freeipa.org) | - | [❌][release:freeipa-issuer] | ✔️ | +| 🥉 | kms-issuer | [📄][config:kms-issuer] | [AWS KMS](https://aws.amazon.com/kms/) | - | [❌][release:kms-issuer] | ✔️ | +| 🥉 | origin-ca-issuer | [📄][config:origin-ca-issuer] | [Cloudflare Origin CA](https://developers.cloudflare.com/ssl/origin-configuration/origin-ca) | - | [❌][release:origin-ca-issuer] | ✔️ | +
              + +[production:venafi-enhanced-issuer]: https://platform.jetstack.io/documentation/academy/issue-and-approve-certificates-with-venafi-control-plane +[production:acme-issuer]: ../tutorials/getting-started-aks-letsencrypt/README.md + +[//]: # (Configuration docs) + +[config:venafi-enhanced-issuer]: https://platform.jetstack.io/documentation/configuration/venafi-enhanced-issuer +[config:acme-issuer]: ./acme.md + +[config:aws-privateca-issuer]: https://github.com/cert-manager/aws-privateca-issuer +[config:selfsigned-issuer]: ./selfsigned.md +[config:ca-issuer]: ./ca.md +[config:vault-issuer]: ./vault.md +[config:venafi-issuer]: ./venafi.md +[config:step-issuer]: https://github.com/smallstep/step-issuer +[config:origin-ca-issuer]: https://github.com/cloudflare/origin-ca-issuer +[config:ncm-issuer]: https://github.com/nokia/ncm-issuer +[config:tcs-issuer]: https://github.com/intel/trusted-certificate-issuer +[config:google-cas-issuer]: https://github.com/jetstack/google-cas-issuer +[config:ejbca-issuer]: https://github.com/Keyfactor/ejbca-cert-manager-issuer +[config:command-issuer]: https://github.com/Keyfactor/command-cert-manager-issuer + +[config:kms-issuer]: https://github.com/Skyscanner/kms-issuer +[config:freeipa-issuer]: https://github.com/guilhem/freeipa-issuer +[config:adcs-issuer]: https://github.com/nokia/adcs-issuer +[config:cfssl-issuer]: https://gerrit.wikimedia.org/r/plugins/gitiles/operations/software/cfssl-issuer + +[//]: # (Release pages) + +[release:venafi-enhanced-issuer]: https://platform.jetstack.io/documentation/installation/venafi-enhanced-issuer/ +[release:cert-manager]: ../releases/README.md + +[release:aws-privateca-issuer]: https://github.com/cert-manager/aws-privateca-issuer/releases +[release:step-issuer]: https://github.com/smallstep/step-issuer/releases +[release:origin-ca-issuer]: https://github.com/cloudflare/origin-ca-issuer/releases +[release:ncm-issuer]: https://github.com/nokia/ncm-issuer/releases +[release:tcs-issuer]: https://github.com/intel/trusted-certificate-issuer/releases +[release:google-cas-issuer]: https://github.com/jetstack/google-cas-issuer/releases +[release:ejbca-issuer]: https://github.com/Keyfactor/ejbca-cert-manager-issuer/tags +[release:command-issuer]: https://github.com/Keyfactor/command-cert-manager-issuer/releases + +[release:kms-issuer]: https://github.com/Skyscanner/kms-issuer/releases +[release:freeipa-issuer]: https://github.com/guilhem/freeipa-issuer/releases +[release:adcs-issuer]: https://github.com/nokia/adcs-issuer/releases +[release:cfssl-issuer]: https://gerrit.wikimedia.org/r/plugins/gitiles/operations/software/cfssl-issuer/+refs + +- The issuers are sorted by their tier and then alphabetically. +- "in-tree" issuers are issuers that are shipped with cert-manager itself. +- These issuers are known to support and honor [approval](https://cert-manager.io/docs/concepts/certificaterequest/#approval). + +If you've created an issuer which you'd like to share, +[raise a Pull Request](https://github.com/cert-manager/website/pulls) to have it added here! + +## Building New External Issuers + +If you're interested in building a new external issuer, check the [development documentation](../contributing/external-issuers.md). + +## Issuer Tier system + +The cert-manager project has a tier system for issuers. This is to help users +understand the maturity of the issuer. +The tiers are 🥇, 🥈 and 🥉. + +NOTE: The cert-manager maintainers can decide to change the criteria and number +of tiers at any time. + +### 🥇 Tier (Production-ready) + +- 🥈 Tier criteria. +- The issuer has an end-to-end tutorial on how to set it up with cert-manager for use in production. +At the time of checking all tutorials[^1], the used cert-manager version has to be still supported (see [Supported Releases](../releases/README.md)) + +### 🥈 Tier (Maintained) + +- The issuer has had a release in the last 3 months (at the time of checking all issuers[^2]). + +### 🥉 Tier (Unmaintained) + +Other + +[^1]: checked on 12th of October 2023 +[^2]: checked on 12th of October 2023 diff --git a/content/docs/contributing/external-issuers.md b/content/docs/contributing/external-issuers.md index f4408e1c15..b7f141a4f9 100644 --- a/content/docs/contributing/external-issuers.md +++ b/content/docs/contributing/external-issuers.md @@ -10,8 +10,8 @@ Since the number of potential issuers is larger than what could reasonably be su main cert-manager repository, cert-manager also supports out-of-tree external issuers, and treats them the same as in-tree issuer types. -This document is for people looking to _create_ external issuers. For more information on how to -install and configure external issuer types, read the [configuration documentation](../configuration/external.md). +This document is for people looking to _create_ external issuers. +For a list of example external issuers, see the [issuers page](../configuration/issuers.md). ## General Overview diff --git a/content/docs/manifest.json b/content/docs/manifest.json index dd80672996..fa322c12c3 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -319,87 +319,92 @@ "path": "/docs/configuration/README.md" }, { - "title": "SelfSigned", - "path": "/docs/configuration/selfsigned.md" + "title": "Issuers", + "path": "/docs/configuration/issuers.md" }, { - "title": "CA", - "path": "/docs/configuration/ca.md" - }, - { - "title": "Vault", - "path": "/docs/configuration/vault.md" - }, - { - "title": "Venafi", - "path": "/docs/configuration/venafi.md" - }, - { - "title": "External", - "path": "/docs/configuration/external.md" - }, - { - "title": "ACME", + "title": "In-tree Issuer Config", "routes": [ { - "title": "Introduction", - "path": "/docs/configuration/acme/README.md" + "title": "SelfSigned", + "path": "/docs/configuration/selfsigned.md" }, { - "title": "HTTP01", - "routes": [ - { - "title": "Introduction", - "path": "/docs/configuration/acme/http01/README.md" - }, - { - "title": "External Load Balancer", - "path": "/docs/configuration/acme/http01/externalloadbalancer.md" - } - ] + "title": "CA", + "path": "/docs/configuration/ca.md" + }, + { + "title": "Vault", + "path": "/docs/configuration/vault.md" + }, + { + "title": "Venafi", + "path": "/docs/configuration/venafi.md" }, { - "title": "DNS01", + "title": "ACME", "routes": [ { "title": "Introduction", - "path": "/docs/configuration/acme/dns01/README.md" - }, - { - "title": "ACMEDNS", - "path": "/docs/configuration/acme/dns01/acme-dns.md" - }, - { - "title": "Akamai", - "path": "/docs/configuration/acme/dns01/akamai.md" - }, - { - "title": "AzureDNS", - "path": "/docs/configuration/acme/dns01/azuredns.md" - }, - { - "title": "Cloudflare", - "path": "/docs/configuration/acme/dns01/cloudflare.md" - }, - { - "title": "DigitalOcean", - "path": "/docs/configuration/acme/dns01/digitalocean.md" - }, - { - "title": "Google CloudDNS", - "path": "/docs/configuration/acme/dns01/google.md" - }, - { - "title": "RFC-2136", - "path": "/docs/configuration/acme/dns01/rfc2136.md" + "path": "/docs/configuration/acme/README.md" }, { - "title": "Route53", - "path": "/docs/configuration/acme/dns01/route53.md" + "title": "HTTP01", + "routes": [ + { + "title": "Introduction", + "path": "/docs/configuration/acme/http01/README.md" + }, + { + "title": "External Load Balancer", + "path": "/docs/configuration/acme/http01/externalloadbalancer.md" + } + ] }, { - "title": "Webhook", - "path": "/docs/configuration/acme/dns01/webhook.md" + "title": "DNS01", + "routes": [ + { + "title": "Introduction", + "path": "/docs/configuration/acme/dns01/README.md" + }, + { + "title": "ACMEDNS", + "path": "/docs/configuration/acme/dns01/acme-dns.md" + }, + { + "title": "Akamai", + "path": "/docs/configuration/acme/dns01/akamai.md" + }, + { + "title": "AzureDNS", + "path": "/docs/configuration/acme/dns01/azuredns.md" + }, + { + "title": "Cloudflare", + "path": "/docs/configuration/acme/dns01/cloudflare.md" + }, + { + "title": "DigitalOcean", + "path": "/docs/configuration/acme/dns01/digitalocean.md" + }, + { + "title": "Google CloudDNS", + "path": "/docs/configuration/acme/dns01/google.md" + }, + { + "title": "RFC-2136", + "path": "/docs/configuration/acme/dns01/rfc2136.md" + }, + { + "title": "Route53", + "path": "/docs/configuration/acme/dns01/route53.md" + }, + { + "title": "Webhook", + "path": "/docs/configuration/acme/dns01/webhook.md" + } + ] } ] } diff --git a/content/docs/policy/approval/approver-policy/README.md b/content/docs/policy/approval/approver-policy/README.md index 76131015f7..f026e0f01b 100644 --- a/content/docs/policy/approval/approver-policy/README.md +++ b/content/docs/policy/approval/approver-policy/README.md @@ -66,7 +66,7 @@ helm upgrade -i -n cert-manager cert-manager-approver-policy jetstack/cert-manag ``` If you are using approver-policy with [external -issuers](../../../configuration/external.md), you _must_ +issuers](../../../configuration/issuers.md), you _must_ include their signer names so that approver-policy has permissions to approve and deny CertificateRequests that [reference them](../../../usage/certificaterequest.md#rbac-syntax). diff --git a/public/_redirects b/public/_redirects index 199af9c005..41bac3ba1c 100644 --- a/public/_redirects +++ b/public/_redirects @@ -221,3 +221,6 @@ https://docs.cert-manager.io/* https://cert-manager.io/docs/:splat 302! # Moved the concept pages into the main website /docs/concepts/certificaterequest/ /docs/usage/certificaterequest/ 301! + +# Moved the external issuer section to the main issuers page +/docs/configuration/external/ /docs/configuration/issuers/ 301! diff --git a/styles/global.scss b/styles/global.scss index 0f8c3db749..482b2e7b2a 100644 --- a/styles/global.scss +++ b/styles/global.scss @@ -151,3 +151,21 @@ a.hidden-link { .DocSearch-SearchBar { margin-bottom: 5px; } + +div.rotate th:nth-child(5),th:nth-child(6),th:nth-child(7),th:nth-child(8) { + writing-mode: vertical-rl; + padding-top: 1.3em; + padding-bottom: 0; + line-height: 1.2em; + text-align: left; + vertical-align: middle; +} + +div.rotate th:nth-child(1),th:nth-child(3),td:nth-child(1),td:nth-child(3),td:nth-child(5),td:nth-child(6),td:nth-child(7),td:nth-child(8) { + text-align: center; +} + +div.rotate table, th, td { + border: 0px; + border-collapse: collapse; +} From 4f4d5c72aeeeeefd09389f79b3a5664f173d3f25 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Wed, 25 Oct 2023 16:02:55 +0200 Subject: [PATCH 254/264] reorder sections on issuers page Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/configuration/issuers.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/configuration/issuers.md b/content/docs/configuration/issuers.md index a6d925525c..3998bb5ce6 100644 --- a/content/docs/configuration/issuers.md +++ b/content/docs/configuration/issuers.md @@ -80,10 +80,6 @@ The following list contains all known cert-manager issuer integrations. If you've created an issuer which you'd like to share, [raise a Pull Request](https://github.com/cert-manager/website/pulls) to have it added here! -## Building New External Issuers - -If you're interested in building a new external issuer, check the [development documentation](../contributing/external-issuers.md). - ## Issuer Tier system The cert-manager project has a tier system for issuers. This is to help users @@ -109,3 +105,7 @@ Other [^1]: checked on 12th of October 2023 [^2]: checked on 12th of October 2023 + +## Building New External Issuers + +If you're interested in building a new external issuer, check the [development documentation](../contributing/external-issuers.md). From 333a9d4f11739b1e3a845f9761f7ef727c1184df Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:59:54 +0200 Subject: [PATCH 255/264] improvement based on feedback: update tier-2 requirement from 3 months to 12 months & add more info about what the e2e docs should look like Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/configuration/issuers.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/content/docs/configuration/issuers.md b/content/docs/configuration/issuers.md index 3998bb5ce6..d5222d52ff 100644 --- a/content/docs/configuration/issuers.md +++ b/content/docs/configuration/issuers.md @@ -6,7 +6,7 @@ description: 'cert-manager configuration: Issuers' The following list contains all known cert-manager issuer integrations.
              -| Tier | Controller | Docs | Issuer | cert-manager
              version used
              in tutorial[^1] | Released within
              3 months[^2] | Is Open Source | +| Tier | Controller | Docs | Issuer | cert-manager
              version used
              in tutorial[^1] | Released within
              12 months[^2] | Is Open Source | |------|------------|------|--------|--------|--------|--------| | 🥇 | acme-issuer (in-tree) | [📄][config:acme-issuer] | [ACME](https://datatracker.ietf.org/doc/html/rfc8555) | [latest][production:acme-issuer] | [✔️][release:cert-manager] | ✔️ | | 🥇 | venafi-enhanced-issuer | [📄][config:venafi-enhanced-issuer] | [Venafi TLS Protect](https://venafi.com/tls-protect/) | [v1.12.1][production:venafi-enhanced-issuer] | [✔️][release:venafi-enhanced-issuer] | ❌ | @@ -93,11 +93,16 @@ of tiers at any time. - 🥈 Tier criteria. - The issuer has an end-to-end tutorial on how to set it up with cert-manager for use in production. -At the time of checking all tutorials[^1], the used cert-manager version has to be still supported (see [Supported Releases](../releases/README.md)) +At the time of checking[^1], the used cert-manager version has to be still supported (see [Supported Releases](../releases/README.md)). +An end-to-end tutorial must include: + 1. a short explanation on how to install cert-manager (including the used version and a link to [https://cert-manager.io/docs/installation/](../installation/)) + 2. all required steps to install the issuer + 3. an explanation on how to configure the issuer's Custom Resources + 4. an explanation on how to issue a certificate using the issuer (using a Certificate resource) ### 🥈 Tier (Maintained) -- The issuer has had a release in the last 3 months (at the time of checking all issuers[^2]). +- The issuer has had a release in the last 12 months (at the time of checking all issuers[^2]). ### 🥉 Tier (Unmaintained) From e3fa3796d28a5ae144c873d5ba042dd2f0d09031 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 23 Nov 2023 15:46:30 +0100 Subject: [PATCH 256/264] add horizon-issuer Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/configuration/issuers.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/content/docs/configuration/issuers.md b/content/docs/configuration/issuers.md index d5222d52ff..1fe411bfda 100644 --- a/content/docs/configuration/issuers.md +++ b/content/docs/configuration/issuers.md @@ -15,6 +15,7 @@ The following list contains all known cert-manager issuer integrations. | 🥈 | command-issuer | [📄][config:command-issuer] | [Keyfactor Command](https://www.keyfactor.com/products/command/) | - | [✔️][release:command-issuer] | ✔️ | | 🥈 | ejbca-issuer | [📄][config:ejbca-issuer] | [EJBCA](https://www.ejbca.org/) | - | [✔️][release:ejbca-issuer] | ✔️ | | 🥈 | google-cas-issuer | [📄][config:google-cas-issuer] | [Google Cloud Certificate
              Authority Service](https://cloud.google.com/certificate-authority-service/) | - | [✔️][release:google-cas-issuer] | ✔️ | +| 🥈 | horizon-issuer | [📄][config:horizon-issuer] | [EVERTRUST Horizon](https://evertrust.fr/horizon) | - | [✔️][release:horizon-issuer] | ✔️ | | 🥈 | ncm-issuer | [📄][config:ncm-issuer] | [Nokia Netguard Certificate Manager](https://www.nokia.com/networks/security-portfolio/netguard/certificate-manager) | - | [✔️][release:ncm-issuer] | ✔️ | | 🥈 | selfsigned-issuer (in-tree) | [📄][config:selfsigned-issuer] | Self-Signed issuer | - | [✔️][release:cert-manager] | ✔️ | | 🥈 | step-issuer | [📄][config:step-issuer] | [Certificate Authority server](https://github.com/smallstep/certificates) | - | [✔️][release:step-issuer] | ✔️ | @@ -48,6 +49,7 @@ The following list contains all known cert-manager issuer integrations. [config:google-cas-issuer]: https://github.com/jetstack/google-cas-issuer [config:ejbca-issuer]: https://github.com/Keyfactor/ejbca-cert-manager-issuer [config:command-issuer]: https://github.com/Keyfactor/command-cert-manager-issuer +[config:horizon-issuer]: https://github.com/evertrust/horizon-issuer [config:kms-issuer]: https://github.com/Skyscanner/kms-issuer [config:freeipa-issuer]: https://github.com/guilhem/freeipa-issuer @@ -67,6 +69,7 @@ The following list contains all known cert-manager issuer integrations. [release:google-cas-issuer]: https://github.com/jetstack/google-cas-issuer/releases [release:ejbca-issuer]: https://github.com/Keyfactor/ejbca-cert-manager-issuer/tags [release:command-issuer]: https://github.com/Keyfactor/command-cert-manager-issuer/releases +[release:horizon-issuer]: https://github.com/evertrust/horizon-issuer/releases [release:kms-issuer]: https://github.com/Skyscanner/kms-issuer/releases [release:freeipa-issuer]: https://github.com/guilhem/freeipa-issuer/releases From 7527bbf96f7665d4fac1c12d3c693ff173d666d2 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:00:48 +0100 Subject: [PATCH 257/264] update VEI docs link Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/configuration/issuers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/configuration/issuers.md b/content/docs/configuration/issuers.md index 1fe411bfda..ce1940b679 100644 --- a/content/docs/configuration/issuers.md +++ b/content/docs/configuration/issuers.md @@ -34,7 +34,7 @@ The following list contains all known cert-manager issuer integrations. [//]: # (Configuration docs) -[config:venafi-enhanced-issuer]: https://platform.jetstack.io/documentation/configuration/venafi-enhanced-issuer +[config:venafi-enhanced-issuer]: https://docs.venafi.cloud/vaas/k8s-components/t-vei-install/ [config:acme-issuer]: ./acme.md [config:aws-privateca-issuer]: https://github.com/cert-manager/aws-privateca-issuer From 1f6c36089079ad9a51ff1b4954891cd71b0cbe01 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:56:05 +0100 Subject: [PATCH 258/264] removed the concepts certificate page and moved remaining info to the 'Requesting Certificates' > 'Certificate' page Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/concepts/certificate.md | 81 ---------------------------- content/docs/manifest.json | 4 -- content/docs/usage/certificate.md | 25 +++++++++ public/_redirects | 1 + 4 files changed, 26 insertions(+), 85 deletions(-) delete mode 100644 content/docs/concepts/certificate.md diff --git a/content/docs/concepts/certificate.md b/content/docs/concepts/certificate.md deleted file mode 100644 index 5b7402f347..0000000000 --- a/content/docs/concepts/certificate.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Certificate -description: 'cert-manager core concepts: Certificates' ---- - -cert-manager has the concept of `Certificates` that define a desired X.509 -certificate which will be renewed and kept up to date. A `Certificate` is a -namespaced resource that references an `Issuer` or `ClusterIssuer` that -determine what will be honoring the certificate request. - -When a `Certificate` is created, a corresponding `CertificateRequest` resource -is created by cert-manager containing the encoded X.509 certificate request, -`Issuer` reference, and other options based upon the specification of the -`Certificate` resource. - -Here is one such example of a `Certificate` resource. - -```yaml -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: acme-crt -spec: - secretName: acme-crt-secret - dnsNames: - - example.com - - foo.example.com - issuerRef: - name: letsencrypt-prod - # We can reference ClusterIssuers by changing the kind here. - # The default value is Issuer (i.e. a locally namespaced Issuer) - kind: Issuer - group: cert-manager.io -``` - -This `Certificate` will tell cert-manager to attempt to use the `Issuer` named -`letsencrypt-prod` to obtain a certificate key pair for the `example.com` and -`foo.example.com` domains. If successful, the resulting TLS key and certificate -will be stored in a secret named `acme-crt-secret`, with keys of `tls.key`, and -`tls.crt` respectively. This secret will live in the same namespace as the -`Certificate` resource. - -When a certificate is issued by an intermediate CA and the `Issuer` can provide -the issued certificate's chain, the contents of `tls.crt` will be the requested -certificate followed by the certificate chain. - -Additionally, if the Certificate Authority is known, the corresponding CA -certificate will be stored in the secret with key `ca.crt`. For example, with -the ACME issuer, the CA is not known and `ca.crt` will not exist in -`acme-crt-secret`. - -cert-manager intentionally avoids adding root certificates to `tls.crt`, because they -are useless in a situation where TLS is being done securely. For more information, -see [RFC 5246 section 7.4.2](https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.2) -which contains the following explanation: - -> Because certificate validation requires that root keys be distributed -> independently, the self-signed certificate that specifies the root -> certificate authority MAY be omitted from the chain, under the -> assumption that the remote end must already possess it in order to -> validate it in any case. - -The `dnsNames` field specifies a list of [`Subject Alternative -Names`](https://en.wikipedia.org/wiki/Subject_Alternative_Name) to be associated -with the certificate. - -The referenced `Issuer` must exist in the same namespace as the `Certificate`. -A `Certificate` can alternatively reference a `ClusterIssuer` which is -non-namespaced and so can be referenced from any namespace. - -You can read more on how to configure your `Certificate` resources -[here](../usage/certificate.md). - -## Certificate Lifecycle - -This diagram shows the lifecycle of a Certificate named `cert-1` using an -ACME / Let's Encrypt issuer. You don't need to understand all of these steps -to use cert-manager; this is more of an explanation of the logic which happens -under the hood for those curious about the process. - -![Life of a Certificate](/images/letsencrypt-flow-cert-manager.png) \ No newline at end of file diff --git a/content/docs/manifest.json b/content/docs/manifest.json index fa322c12c3..6a18b7cb22 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -791,10 +791,6 @@ "title": "Issuer", "path": "/docs/concepts/issuer.md" }, - { - "title": "Certificate", - "path": "/docs/concepts/certificate.md" - }, { "title": "ACME Orders and Challenges", "path": "/docs/concepts/acme-orders-challenges.md" diff --git a/content/docs/usage/certificate.md b/content/docs/usage/certificate.md index 697d282615..264a798067 100644 --- a/content/docs/usage/certificate.md +++ b/content/docs/usage/certificate.md @@ -128,6 +128,31 @@ The `Certificate` will be issued using the issuer named `ca-issuer` in the A full list of the fields supported on the Certificate resource can be found in the [API reference documentation](../reference/api-docs.md#cert-manager.io/v1.CertificateSpec). +### Target Secret + +When a certificate is issued by an intermediate CA and the `Issuer` can provide +the issued certificate's chain, the contents of `tls.crt` will be the requested +certificate followed by the certificate chain. + +Additionally, if the Certificate Authority is known, the corresponding CA +certificate will be stored in the secret with key `ca.crt`. For example, with +the ACME issuer, the CA is not known and `ca.crt` will not exist in the Secret. +The `ca.crt` value at the time of issuance can be copied to the trust store of +the application that is using the certificate. However, DO NOT directly mount +the `ca.crt` value into the application's trust store, as it will be updated +when the certificate is renewed (see [Trusting certificates](../trust/README.md) for more details). + +cert-manager intentionally avoids adding root certificates to `tls.crt`, because they +are useless in a situation where TLS is being done securely. For more information, +see [RFC 5246 section 7.4.2](https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.2) +which contains the following explanation: + +> Because certificate validation requires that root keys be distributed +> independently, the self-signed certificate that specifies the root +> certificate authority MAY be omitted from the chain, under the +> assumption that the remote end must already possess it in order to +> validate it in any case. + ### X.509 key usages and extended key usages diff --git a/public/_redirects b/public/_redirects index 41bac3ba1c..9e90940453 100644 --- a/public/_redirects +++ b/public/_redirects @@ -221,6 +221,7 @@ https://docs.cert-manager.io/* https://cert-manager.io/docs/:splat 302! # Moved the concept pages into the main website /docs/concepts/certificaterequest/ /docs/usage/certificaterequest/ 301! +/docs/concepts/certificate/ /docs/usage/certificate/ 301! # Moved the external issuer section to the main issuers page /docs/configuration/external/ /docs/configuration/issuers/ 301! From b8c4acc2ac051e2a95247d4e7cccdb053da92af3 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:24:43 +0100 Subject: [PATCH 259/264] fix broken links Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/concepts/acme-orders-challenges.md | 2 +- content/docs/tutorials/acme/http-validation.md | 2 +- content/docs/tutorials/acme/nginx-ingress.md | 2 +- .../README.md | 2 +- content/docs/usage/README.md | 2 -- content/docs/usage/certificaterequest.md | 2 +- content/docs/usage/ingress.md | 2 +- 7 files changed, 6 insertions(+), 8 deletions(-) diff --git a/content/docs/concepts/acme-orders-challenges.md b/content/docs/concepts/acme-orders-challenges.md index e7c67a4a6e..f4e1d3e2a5 100644 --- a/content/docs/concepts/acme-orders-challenges.md +++ b/content/docs/concepts/acme-orders-challenges.md @@ -22,7 +22,7 @@ validation can be found on the Let's Encrypt website certificate request which will be created automatically once a new [`CertificateRequest`](../usage/certificaterequest.md) resource referencing an ACME issuer has been created. `CertificateRequest` resources are created -automatically by cert-manager once a [`Certificate`](./certificate.md) resource +automatically by cert-manager once a [`Certificate`](../usage/certificate.md) resource is created, has its specification changed, or needs renewal. As an end-user, you will never need to manually create an `Order` resource. diff --git a/content/docs/tutorials/acme/http-validation.md b/content/docs/tutorials/acme/http-validation.md index b61bf7c14b..5abc937c04 100644 --- a/content/docs/tutorials/acme/http-validation.md +++ b/content/docs/tutorials/acme/http-validation.md @@ -84,7 +84,7 @@ spec: The Certificate resource describes our desired certificate and the possible methods that can be used to obtain it. You can learn more about the Certificate -resource in the [docs](../../concepts/certificate.md). If the certificate is +resource in the [docs](../../usage/certificate.md). If the certificate is obtained successfully, the resulting key pair will be stored in a secret called `example-com-tls` in the same namespace as the Certificate. diff --git a/content/docs/tutorials/acme/nginx-ingress.md b/content/docs/tutorials/acme/nginx-ingress.md index 9a334f4810..c7cf9f1568 100644 --- a/content/docs/tutorials/acme/nginx-ingress.md +++ b/content/docs/tutorials/acme/nginx-ingress.md @@ -237,7 +237,7 @@ when you might choose to use each can be found on [Issuer concepts](../../concep Certificates resources allow you to specify the details of the certificate you want to request. They reference an issuer to define _how_ they'll be issued. -For more information, see [Certificate concepts](../../concepts/certificate.md). +For more information, see [Certificate concepts](../../usage/certificate.md). ## Step 6 - Configure a Let's Encrypt Issuer diff --git a/content/docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md b/content/docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md index 80f233aee7..21bde539ca 100644 --- a/content/docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md +++ b/content/docs/tutorials/getting-started-with-cert-manager-on-google-kubernetes-engine-using-lets-encrypt-for-ingress-ssl/README.md @@ -628,7 +628,7 @@ Events: When you create a Certificate, cert-manager will create a collection of temporary resources which each contain information about the status of certificate signing process. -You can read more about these in the [Certificate Lifecycle](../../concepts/certificate.md#certificate-lifecycle) section. +You can read more about these in the [Certificate Lifecycle](../../usage/certificate.md#certificate-lifecycle) section. Use the `cmctl status` command to view details of all these resources and all the associated Events and error messages. You may see some temporary errors, like: diff --git a/content/docs/usage/README.md b/content/docs/usage/README.md index 59f605379a..15600b5acd 100644 --- a/content/docs/usage/README.md +++ b/content/docs/usage/README.md @@ -11,8 +11,6 @@ Once an [`Issuer`](../configuration/README.md) has been configured, you're ready There are several use cases and methods for requesting certificates through cert-manager: -- [Certificate Resources](./certificate.md): The simplest and most common method for - requesting signed certificates. - [Securing Ingress Resources](./ingress.md): A method to secure ingress resources in your cluster. - [Securing OpenFaaS functions](https://docs.openfaas.com/reference/ssl/kubernetes-with-cert-manager/): diff --git a/content/docs/usage/certificaterequest.md b/content/docs/usage/certificaterequest.md index 82db0ce585..2b5a27c927 100644 --- a/content/docs/usage/certificaterequest.md +++ b/content/docs/usage/certificaterequest.md @@ -105,7 +105,7 @@ This condition should only be set by an approver. namely: `username`, `groups`, `uid`, and `extra`. These values contain the user who created the `CertificateRequest`. This user will be cert-manager itself in the case that the `CertificateRequest` was created by a -[`Certificate`](./certificate.md) resource, or instead the user who created the +[`Certificate`](../usage/certificate.md) resource, or instead the user who created the `CertificateRequest` directly. > **Warning**: These fields are managed by cert-manager and should _never_ be diff --git a/content/docs/usage/ingress.md b/content/docs/usage/ingress.md index 042c57e820..cc1b9fc04a 100644 --- a/content/docs/usage/ingress.md +++ b/content/docs/usage/ingress.md @@ -102,7 +102,7 @@ trigger Certificate resources to be automatically created: This annotation will also add the annotation `"cert-manager.io/issue-temporary-certificate": "true"` onto created certificates which will cause a [temporary - certificate](./certificate.md#temporary-certificates-whilst-issuing) to be set + certificate](../usage/certificate.md#temporary-certificates-whilst-issuing) to be set on the resulting Secret until the final signed certificate has been returned. This is useful for keeping compatibility with the `ingress-gce` component. From e564f8333fdc90418ae4704ef9337ca737205a75 Mon Sep 17 00:00:00 2001 From: Erik Godding Boye Date: Sun, 26 Nov 2023 16:02:49 +0100 Subject: [PATCH 260/264] Add/update docs for new trust-manager features Co-authored-by: Ashley Davis Signed-off-by: Erik Godding Boye --- .spelling | 4 + content/docs/trust/trust-manager/README.md | 39 ++++-- .../docs/trust/trust-manager/api-reference.md | 128 ++++++++++++++++++ scripts/gendocs/generate-trust-manager | 2 +- 4 files changed, 161 insertions(+), 12 deletions(-) diff --git a/.spelling b/.spelling index 15673739a4..320d272075 100644 --- a/.spelling +++ b/.spelling @@ -214,6 +214,7 @@ OpenWRT OperatorHub OperatorHub.io PEM +PKCS12-formatted PKCS#12 PKCS#8 Pomerium @@ -402,6 +403,7 @@ ndegory oauth2 onwards openshift-supported-versions +plaintext powershell pre preemptible @@ -458,6 +460,8 @@ upstream userinfo vhosakot v0.5.0 +v0.7.0 +v0.7.0. v0.16 v0.23.1 v1 diff --git a/content/docs/trust/trust-manager/README.md b/content/docs/trust/trust-manager/README.md index 94a94a416d..3b88dd1957 100644 --- a/content/docs/trust/trust-manager/README.md +++ b/content/docs/trust/trust-manager/README.md @@ -21,7 +21,7 @@ and enables cluster administrators to easily automate providing a secure bundle to worry about rebuilding containers to update trust stores. It's designed to complement cert-manager and works well when consuming CA certificates from a -cert-manager `Issuer` or `ClusterIssuer` but can be used entirely independently from cert-manager +cert-manager `Issuer` or `ClusterIssuer` but can be used entirely independently of cert-manager if needed. ## Usage @@ -70,12 +70,15 @@ spec: # Sync the bundle to a ConfigMap called `my-org.com` in every namespace which # has the label "linkerd.io/inject=enabled" # All ConfigMaps will include a PEM-formatted bundle, here named "root-certs.pem" - # and in this case we also request a binary JKS formatted bundle, here named "bundle.jks" + # and in this case we also request binary formatted bundles in JKS and PKCS#12 formats, + # here named "bundle.jks" and "bundle.p12". configMap: key: "root-certs.pem" additionalFormats: jks: key: "bundle.jks" + pkcs12: + key: "bundle.p12" namespaceSelector: matchLabels: linkerd.io/inject: "enabled" @@ -88,18 +91,23 @@ spec: - `inLine` - a manually specified string containing at least one certificate - `useDefaultCAs` - usually, a bundle of publicly trusted certificates -These sources, along with the single currently supported target type (`configMap`) -are documented in the trust-manager [API reference documentation](./api-reference.md). +`ConfigMap` is the default target type, but as of v0.7.0 trust-manager also supports `Secret` resources as targets. + +Support for `Secret` targets must be explicitly enabled in the trust-manager controller; see details below under "Enable Secret targets". + +All sources and target options are documented in the trust-manager [API reference documentation](./api-reference.md). #### Targets -All `Bundle` targets are written to `ConfigMap`s whose name matches that of the `Bundle`, and every -target has a PEM-formatted bundle included. +All `Bundle` targets are written to `ConfigMap`s (and/or `Secret`s) whose name matches that of the +`Bundle`, and every target has a PEM-formatted bundle included. + +Users can also optionally choose to write JKS/PKCS#12 formatted binary trust store(s) to targets. +JKS has been supported since v0.5.0, and PKCS#12 since v0.7.0. -Users can also optionally - as of trust-manager v0.5.0 - choose to write a JKS formatted binary -bundle to the target. We understand that most Java applications tend to require a password on JKS -files (even though trust bundles don't contain secrets), so all trust-manager JKS bundles use the -default password `changeit`. +Applications consuming JKS and PKCS#12 trust stores often require a password to be set for legacy reasons. These passwords are often security theater - either they use very weak encryption or the passwords are provided in plaintext next to the files they encrypt which defeats the purpose of having them. + +Trust bundles do not contain private keys, and so for most use cases there wouldn't be any security benefit to encrypting them. As such, passwords for trust stores are hard-coded to `changeit` for JKS and `""` (the empty string or "password-less") for PKCS#12. Future releases of trust-manager may make these passwords configurable. #### Namespace Selector @@ -132,7 +140,16 @@ helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set install helm upgrade -i -n cert-manager trust-manager jetstack/trust-manager --wait ``` -### approver-policy Integration +#### Enable Secret targets + +`Secret` targets are supported as of trust-manager v0.7.0, but need to be explicitly enabled on the controller. +The feature can be enabled with a Helm value `--set secretTargets.enabled=true`, but since the controller needs +RBAC to read and update secrets, you also need to set `secretTargets.authorizedSecretsAll` or `secretTargets.authorizedSecrets`. +Please consult the +[trust-manager Helm chart docs](https://github.com/cert-manager/trust-manager/blob/main/deploy/charts/trust-manager/README.md#values) +for details and trade-offs. + +#### approver-policy Integration If you're running [approver-policy](../../policy/approval/approver-policy/README.md) then cert-manager's default approver will be disabled which will mean that trust-manager's webhook certificate will - by default - block when you install the Helm chart until it's manually approved. diff --git a/content/docs/trust/trust-manager/api-reference.md b/content/docs/trust/trust-manager/api-reference.md index da09c7c039..d0448f061f 100644 --- a/content/docs/trust/trust-manager/api-reference.md +++ b/content/docs/trust/trust-manager/api-reference.md @@ -246,6 +246,13 @@ Target is the target location in all namespaces to sync source data to. NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
              false
              secretobject + Secret is the target Secret that all Bundle source data will be synced to. Using Secrets as targets is only supported if enabled at trust-manager startup. By default, trust-manager has no permissions for writing to secrets and can only read secrets in the trust namespace.
              +
              false
              @@ -271,6 +278,13 @@ AdditionalFormats specifies any additional formats to write to the target JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit".
              false + + pkcs12 + object + + PKCS12 requests a PKCS12-formatted binary trust bundle to be written to the target. The bundle is created without a password.
              + + false @@ -300,6 +314,31 @@ JKS requests a JKS-formatted binary trust bundle to be written to the target. Th +### `Bundle.spec.target.additionalFormats.pkcs12` + + +PKCS12 requests a PKCS12-formatted binary trust bundle to be written to the target. The bundle is created without a password. + + + + + + + + + + + + + + + + +
              NameTypeDescriptionRequired
              keystring + Key is the key of the entry in the object's `data` field to be used.
              +
              true
              + + ### `Bundle.spec.target.configMap` @@ -350,6 +389,31 @@ NamespaceSelector will, if set, only sync the target resource in Namespaces whic +### `Bundle.spec.target.secret` + + +Secret is the target Secret that all Bundle source data will be synced to. Using Secrets as targets is only supported if enabled at trust-manager startup. By default, trust-manager has no permissions for writing to secrets and can only read secrets in the trust namespace. + + + + + + + + + + + + + + + + +
              NameTypeDescriptionRequired
              keystring + Key is the key of the entry in the object's `data` field to be used.
              +
              true
              + + ### `Bundle.status` @@ -488,6 +552,13 @@ Target is the current Target that the Bundle is attempting or has completed sync NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
              false + + secret + object + + Secret is the target Secret that all Bundle source data will be synced to. Using Secrets as targets is only supported if enabled at trust-manager startup. By default, trust-manager has no permissions for writing to secrets and can only read secrets in the trust namespace.
              + + false @@ -513,6 +584,13 @@ AdditionalFormats specifies any additional formats to write to the target JKS requests a JKS-formatted binary trust bundle to be written to the target. The bundle is created with the hardcoded password "changeit".
              false + + pkcs12 + object + + PKCS12 requests a PKCS12-formatted binary trust bundle to be written to the target. The bundle is created without a password.
              + + false @@ -542,6 +620,31 @@ JKS requests a JKS-formatted binary trust bundle to be written to the target. Th +### `Bundle.status.target.additionalFormats.pkcs12` + + +PKCS12 requests a PKCS12-formatted binary trust bundle to be written to the target. The bundle is created without a password. + + + + + + + + + + + + + + + + +
              NameTypeDescriptionRequired
              keystring + Key is the key of the entry in the object's `data` field to be used.
              +
              true
              + + ### `Bundle.status.target.configMap` @@ -590,3 +693,28 @@ NamespaceSelector will, if set, only sync the target resource in Namespaces whic false + + +### `Bundle.status.target.secret` + + +Secret is the target Secret that all Bundle source data will be synced to. Using Secrets as targets is only supported if enabled at trust-manager startup. By default, trust-manager has no permissions for writing to secrets and can only read secrets in the trust namespace. + + + + + + + + + + + + + + + + +
              NameTypeDescriptionRequired
              keystring + Key is the key of the entry in the object's `data` field to be used.
              +
              true
              diff --git a/scripts/gendocs/generate-trust-manager b/scripts/gendocs/generate-trust-manager index e4ee7cbb40..3125d37aba 100755 --- a/scripts/gendocs/generate-trust-manager +++ b/scripts/gendocs/generate-trust-manager @@ -59,6 +59,6 @@ gendocs() { echo "+++ Cloning trust-manager repository..." git clone "https://github.com/cert-manager/trust-manager.git" "$tmpdir" -checkout "v0.6.0" +checkout "v0.7.0" gendocs "$REPO_ROOT/content/docs/trust/trust-manager/api-reference.md" From 29b7ea14998dbb1bc58865b5332d05d8bd141ff0 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:08:58 +0100 Subject: [PATCH 261/264] add a concrete example on how to restore a cert-manager installation Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/devops-tips/backup.md | 42 +++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/content/docs/devops-tips/backup.md b/content/docs/devops-tips/backup.md index 8a1f9f69fa..435e0c35ed 100644 --- a/content/docs/devops-tips/backup.md +++ b/content/docs/devops-tips/backup.md @@ -101,18 +101,16 @@ to a situation where updates to the `Ingress` (i.e a new DNS name) are not applied to the `Certificate`. To avoid this issue, in most cases `Certificate`s created via `ingress-shim` -can be excluded from the backup. Given that the restore happens +should be excluded from the backup. Given that the restore happens in the correct order (`Secret` with the X.509 certificate restored before the `Ingress`) `cert-manager` will be able to create a new `Certificate` for the `Ingress` and determine that the existing `Secret` is for that `Certificate`. ### Velero -We have briefly tested backup and restore with `velero` `v1.5.3` and -`cert-manager` versions `v1.3.1` and `v1.3.0` as well as `velero` `v1.3.1` - and `cert-manager` `v1.1.0`. +We have tested backup and restore with `velero` `v1.12.2` and `cert-manager` version `v1.13.2`. - A few potential edge cases: +A few potential edge cases: - Ensure that the backups include `cert-manager` CRDs. For example, we have seen that if `--exclude-namespaces` flag is passed to @@ -124,7 +122,7 @@ We have briefly tested backup and restore with `velero` `v1.5.3` and exclude `Order`s, `Challenge`s and `CertificateRequest`s from the backup, see [Excluding some cert-manager resources from backup](#excluding-some-cert-manager-resources-from-backup). -- Velero's [default restore order](https://github.com/vmware-tanzu/velero/blob/main/pkg/cmd/server/server.go#L470)(`Secrets` before `Ingress`es, Custom Resources +- Velero's [default restore order](https://github.com/vmware-tanzu/velero/blob/main/pkg/cmd/server/server.go#L470) (`Secrets` before `Ingress`es, Custom Resources restored last), should ensure that there is no unnecessary certificate reissuance due to the order of restore operation, see [Order of restore](#order-of-restore). @@ -140,6 +138,38 @@ We have briefly tested backup and restore with `velero` `v1.5.3` and `Certificate`s created for `Ingress`es from the backup even when not re-creating the `Ingress` itself. See [Restoring Ingress Certificates](#restoring-ingress-certificates). + +#### Example backup and restore using Velero + +The following command will create a backup of all Kubernetes resources in the +default and cert-manager namespaces, excluding `Order`s, `Challenge`s and +`CertificateRequest`s (see above): +```bash +velero backup create \ + full-backup \ + --include-namespaces cert-manager,default \ + --include-cluster-resources=true \ + --exclude-resources challenges.acme.cert-manager.io,orders.acme.cert-manager.io,certificaterequests.cert-manager.io +``` + +We recommend that you restore the backup in two steps, first restoring the +`Secret`s and `Ingress`es and the `cert-manager` deployment, and then restoring +the rest of the resources. This is because `cert-manager`'s controller needs to +be able to create `Certificate` for the ingresses, so the owner reference is set. + +1. Restore everything except `Certificate` resources: +```bash +velero restore create \ + --from-backup full-backup \ + --exclude-resources certificates.cert-manager.io +``` + +2. Wait for cert-manager to create the `Certificate`s for the `Ingress`es (if cert-manager is having RBAC/ webhook issues, you might have to manually restart the deployments). After the auto-generated `Certificate`s are created, restore the manually created `Certificate`s: +```bash +velero restore create \ + --from-backup full-backup +``` + ## Backing up CertificateRequests We no longer recommend including `CertificateRequest` resources in a backup From 6e597d42fc246a312fad0e8b09578ae5ab541bd4 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:14:28 +0100 Subject: [PATCH 262/264] improve backup docs based on feedback Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- content/docs/devops-tips/backup.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/content/docs/devops-tips/backup.md b/content/docs/devops-tips/backup.md index 435e0c35ed..6fa08ef954 100644 --- a/content/docs/devops-tips/backup.md +++ b/content/docs/devops-tips/backup.md @@ -122,7 +122,7 @@ A few potential edge cases: exclude `Order`s, `Challenge`s and `CertificateRequest`s from the backup, see [Excluding some cert-manager resources from backup](#excluding-some-cert-manager-resources-from-backup). -- Velero's [default restore order](https://github.com/vmware-tanzu/velero/blob/main/pkg/cmd/server/server.go#L470) (`Secrets` before `Ingress`es, Custom Resources +- Velero's [default restore order](https://github.com/vmware-tanzu/velero/blob/a318e1da995a390c9f10e4aef7df356594944377/pkg/cmd/server/server.go#L511-L543) (`Secrets` before `Ingress`es, Custom Resources restored last), should ensure that there is no unnecessary certificate reissuance due to the order of restore operation, see [Order of restore](#order-of-restore). @@ -152,10 +152,13 @@ velero backup create \ --exclude-resources challenges.acme.cert-manager.io,orders.acme.cert-manager.io,certificaterequests.cert-manager.io ``` -We recommend that you restore the backup in two steps, first restoring the -`Secret`s and `Ingress`es and the `cert-manager` deployment, and then restoring -the rest of the resources. This is because `cert-manager`'s controller needs to -be able to create `Certificate` for the ingresses, so the owner reference is set. +To workaround Velero not restoring owner references, you can restore the backup +in two steps: first restore the `Secret`s and `Ingress`es and the `cert-manager` +deployment, second restore the `Certificate` resources. This will allow `cert-manager`'s +controller to create the `Certificate` for the ingresses and set the owner reference. +The second restore will then restore the manually created `Certificate`s and detect that +the generated `Certificate`s for the `Ingress`es already exist and will not attempt to +recreate them. 1. Restore everything except `Certificate` resources: ```bash From d30d3361aab4c7e85a7486bec151d93970fcb08c Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Fri, 1 Dec 2023 13:27:41 +0000 Subject: [PATCH 263/264] add FAQ entry on keystore passwords Signed-off-by: Ashley Davis --- content/docs/faq/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/content/docs/faq/README.md b/content/docs/faq/README.md index 6f885591a7..4355dd1be1 100644 --- a/content/docs/faq/README.md +++ b/content/docs/faq/README.md @@ -150,6 +150,35 @@ spec: Default `duration` is [90 days](https://github.com/cert-manager/cert-manager/blob/v1.2.0/pkg/apis/certmanager/v1/const.go#L26). If `renewBefore` has not been set, `Certificate` will be renewed 2/3 through its _actual_ duration. +### Why do you say that passwords on JKS or PKCS#12 files aren't helpful? + +#### Simple Answer + +"Passwords" on PKCS#12 or JKS files are almost always security theater, and they're only needed to support applications which are unable to parse password-less versions of these files. Even if you use a secure password for these files (which is rare), weak encryption algorithms and the management of the underlying material usually invalidate the secure password. + +We recommend that you treat these passwords as legacy implementation details, and use short hard-coded strings for these passwords when you're +forced to use one. Don't spend time trying to generate or handle "secure" passwords for these files - simply choose a constant such as `changeit` or `notapassword123` and use that for every PKCS#12 or JKS bundle you generate. + +#### Longer Answer + +Lots of people see the word "password" when handling JKS or PKCS#12 bundles and they draw the obvious +conclusion that it's a valuable security resource which needs to be handled carefully. + +This is generally not the case - not only are these passwords not really passwords, but they're also vanishingly unlikely to be meaningful for security of any kind. + +Mostly, these passwords exist only because some applications require some password to be set. That requirement is the sole reason for cert-manager and its sub-projects supporting setting a password on these types of bundles. + +There are several main reasons why we don't consider these passwords to be security critical: + +1. Most applications which use these passwords will mount the file containing the password in plain text right next to the bundle which uses it, with the same permissions and access control. This would make even the most secure password completely pointless as a security measure. +2. Most PKCS#12 and JKS bundles which are encrypted use extremely old encryption algorithms which are fundamentally insecure +3. The word "password" leads people to think of human-memorable passwords, which are not appropriate for this kind of encryption. This means that the passwords used are often themselves insecure in this context. +4. When we generate PKCS#12 or JKS files, they almost always live in the same Secret as an unencrypted private key anyway! + +Without a very detailed threat model and putting serious time into your system's architecture in an extremely paranoid way, spending time on these "passwords" is going to be a red herring time sink with little to no return. Your efforts would almost always be better spent on securing systems through other methods. + +See "simple answer" above for usage guidelines for these "passwords". + ## Miscellaneous ### Kubernetes has a builtin `CertificateSigningRequest` API. Why not use that? From 77589e12b7c5a9ee2eec3a83058575972e92eb60 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Thu, 7 Dec 2023 10:34:09 +0000 Subject: [PATCH 264/264] add permanent link to keystore passwords also adds a little more context Signed-off-by: Ashley Davis --- content/docs/faq/README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/content/docs/faq/README.md b/content/docs/faq/README.md index 4355dd1be1..6e3dd7b240 100644 --- a/content/docs/faq/README.md +++ b/content/docs/faq/README.md @@ -150,7 +150,16 @@ spec: Default `duration` is [90 days](https://github.com/cert-manager/cert-manager/blob/v1.2.0/pkg/apis/certmanager/v1/const.go#L26). If `renewBefore` has not been set, `Certificate` will be renewed 2/3 through its _actual_ duration. -### Why do you say that passwords on JKS or PKCS#12 files aren't helpful? + +### Why are passwords on JKS or PKCS#12 files not helpful? + +This question comes in many forms, including: + +- "Why is it OK to hard code these passwords?" +- "Do I need to keep these passwords secure?" +- "Are these passwords used in a secure way?" + +Specifically, this FAQ talks about passwords for PKCS#12 and JKS "keystores". #### Simple Answer